Chapter 3 outlined the REALbasic object model, explaining what classes and instances are, and how you work with them in REALbasic. We saw that in the IDE you edit classes; then the running program generates instances and sends messages between them. We talked about the message-sending mechanism, how instances are generated, and how your code can refer to the instance it wants to send a message to. Now we’re going to talk about the notion of classes in more depth, and in particular about relationships between classes, other class features, and how to do things with classes. Also discussed are modules, which provide methods, properties, and constants that are available from anywhere. The chapter ends by describing a few useful example classes that you can make in the comfort and safety of your own home.
A class may be a type of some other class. When two classes are related in this way, the first is said to be a subclass of the second, and the second is said to be the superclass of the first. For example, if you have a Triangle class, you might also have an Isosceles class, where Isosceles is a type of Triangle. Isosceles is then a subclass of Triangle, and Triangle is the superclass of Isosceles. A class can have many subclasses (for example, Triangle might also have a Scalene subclass), but every subclass has exactly one immediate superclass. The subclass-superclass relationship is sometimes called "Is-A,” because every instance of the subclass also is an instance of the superclass; for example, every Isosceles is a Triangle. This sort of relationship also brings with it the notion of inheritance , meaning that a subclass is everything the superclass is, and then some. For example, a Triangle has three sides; so does an Isosceles, but it adds a rule that two of the sides are equal.
A class may be declared to qualify as also being some other class, without inheritance, and without the other class having any real existence; the other class is just a name. The first class is said to implement the second, and the second class is not a real class at all, but a class interface . For example, we might have reason to want a Triangle, a Face, and a StopSign to be DrawableThings, as a matter of nomenclature, but to have nothing else in common. A class can implement multiple class interfaces, and can implement class interfaces even if it is a subclass, so this is also a way of getting around the rule that says a subclass can’t have more than one immediate superclass. We may think of this as an " Also Is-A” relationship.
A class may have a property whose datatype is some other class. For example, there might be a Point class and a Line class; you could define the Line class as having two Point properties (because two points determine a line). This relationship, especially when the bond between the class and the properties is felt to be particularly strong, is sometimes called " Has-A.” So here, a Line has two Points and just wouldn’t be a Line without them, and perhaps our program doesn’t use Points except as features of a Line; that’s a good solid Has-A relationship. Another kind of “Has-A” relationship is where a class has a property that is of a different class, and also provides all the methods for working with that property; this is called a wrapper . An example appears later in this chapter.
What classes you create for your project, and in particular how you set up the relationships among them, is a matter of design—object-oriented design. Your project ends up with an internal architecture of classes. There will be a class hierarchy: A and AA are subclasses of B, B and BB are subclasses of C, and so forth. Cutting across this hierarchy you might have some class interfaces, giving AA and BB some special extra commonality. D might operate only as a property of A, encapsulating functionality or building a data structure. The principles of object-oriented design are a mixture of science, art, philosophy, and expediency; it’s a big subject, too big for this book. But the first step is to understand REALbasic’s object model, how classes can relate and what you can do with these relationships; that’s what this chapter is about.
Relationships among classes in REALbasic, especially the class hierarchy, are not merely a convenience of design; they are crucial. REALbasic’s application framework provides a hierarchy of built-in classes before you write any code at all; and adding to that hierarchy is how you take advantage of the built-in functionality of those classes. For example, you might want a class that acts just like a built-in PushButton but does a few things in addition. To get it, you’d make a subclass of PushButton. That’s what the next section is about.
To create a new class, choose File → New Class. A listing for the new class will appear in the Project Window. If you immediately hit the Tab key, you’ll be transported to the Properties Window, ready to give the new class a meaningful name.
When you create a new class, you may declare it to be a subclass of some other class, by setting, in the Properties Window, the new class’s Super. To do so, you choose from a popup menu which lists all subclassable built-in classes and all classes you’ve added to this project. This specifies your new class’s superclass, and thus makes your new class a subclass of that other class. It is also possible to specify that a class is to be a subclass of no other class; to do this, choose “<none>” in the Super popup (this is the default when you create a new class).
As we’ve already said, a subclass relates to its superclass through the medium of inheritance . Simply put, this means that an instance of the subclass also is an instance of the superclass (”Is-A”). More formally, the subclass has all the same methods and properties as the superclass, and an instance of the subclass can be sent all the same messages as an instance of the superclass. Furthermore, in the case of one of REALbasic’s built-in classes that receives events as part of the application framework, a subclass receives those events as well. Subclassing is thus a quick and easy way to take advantage of an existing class’s functionality. And most of REALbasic’s built-in classes are part of its application framework, meaning that their functionality includes powerful stuff like displaying interface items on the screen or communicating over the Internet; so the ability to subclass such classes means that you can make some very powerful subclasses.
Unfortunately, aside from the Super listing in the Properties Window, the nature of the relationship between subclass and superclass is not in any way reflected in the IDE. Looking at a subclass’s Code Editor, you are not shown what methods and properties it inherits from the superclass; to find out, you have to look at the superclass (or its documentation). Nor is there any way to learn what are the subclasses of a given class. In short, the REALbasic IDE doesn’t make inheritance easy to use. This is one of the worst aspects of the IDE’s interface.
If a subclass is like its superclass, why make a new class at all? What makes a subclass a different class from its superclass? Basically, it’s that you can add to a subclass members that its superclass lacks. A subclass is its superclass and then some. In the case of REALbasic’s built-in classes, the ability to add to the subclass is crucial, because you can’t modify the built-in classes; the way you take advantage of the functionality of one of REALbasic’s built-in classes while customizing its structure and functionality is to make a subclass of it and customize that.
For example, recall the arcade game described in Chapter 3. There, we imagined a ScoreBox class, which would have a Score property, and would know how to display its value in a window. Also, the ScoreBox class would have an Increase method, which would increment the value of the Score property. Let’s actually implement the ScoreBox class.
To do so, we’ll take advantage of a built-in Control class called a StaticText, which already has the following useful functionality: it displays in its containing window the value of its own Text property. We can’t modify the StaticText class, so we make a subclass of it: we create a new class, name it ScoreBox, and designate StaticText as its Super. Then, in the ScoreBox class’s Code Editor, we give it a Score property which is an integer, and an Increase method, which goes like this:
Sub increase() self.score = self.score + 1 // increment the score self.showScore // display the score End Sub
We have postponed the question of how the ScoreBox will actually display its score in its containing window, by giving that job to an unwritten subroutine. Let’s write it. It will be a method handler, as we know. A StaticText always displays its own Text property, so it suffices to set the Text property to the value of the Score property. The Text property is a string, while the Score property is an integer, so we must also convert. Here is ScoreBox’s ShowScore method handler:
Sub showScore() self.text = str(self.score) End Sub
Finally, let’s think about what happens when a window
containing a ScoreBox instance first opens. That instance’s
score is autoinitialized to
0, which seems
acceptable. But we also want to make sure it is displayed. Among the
Events listed in ScoreBox’s Code Editor is the
Open event. An
Open event handler, if we choose to write one, is automatically
called by REALbasic when the control is instantiated. That’s an
appropriate moment to start displaying the score. Here is
ScoreBox’s Open event handler:
That’s all there is to it! Let’s try it out. Drag the ScoreBox listing from the Project Window into a Window Editor. A new control appears in the Window Editor; select it. Looking at the Properties Window, you can see that although this control is named StaticText1 by default, its Super listing says that it is indeed a ScoreBox instance. So StaticText1 should know how to accept the Increase message. Let’s see if it does. Drag a PushButton from the Tools Window into the Window Editor; double-click it in the Window Editor to access its Action event handler in the window’s Code Editor, and give it this code:
Run the project in the IDE. There’s a number in the window! It’s zero! And every time you press the button, the number increases!
It’s simple, almost trivial; yet, for the reasons explained in Chapter 3, it’s tremendously powerful. The score is now maintained, appropriately, by the object that primarily operates on it; other objects can call a ScoreBox’s Increase method without worrying about what this does or what the score is; the ScoreBox, for its part, doesn’t care who is calling it; and a project can contain multiple ScoreBox instances, each maintaining a separate score, yet all behaving identically.
We’ve seen that the code for the ScoreBox class’s behavior doesn’t live in the window’s Code Editor; it lives in the ScoreBox class’s own Code Editor, which you access by double-clicking the ScoreBox class’s listing in the Project Window. All ScoreBox instances will look to this code for their behavior, and if you change this code, all ScoreBox instances will henceforward display the new behavior. Let’s prove to ourselves that this is true. (If the project is running in the IDE, you’ll have to kill it first, so that you can modify the project.) Start by dragging the ScoreBox listing from the Project Window into the Window Editor again. Now the window has two ScoreBox instances, StaticText1 and StaticText2. Change the PushButton’s Action event handler to read:
Run the project and push the button repeatedly; both ScoreBox instances behave identically (and they increment together, since they both started at zero and both receive the Increase message when we push the button; I remind you, however, that they maintain independent scores). Now, in the ScoreBox class’s Increase method, change this line:
self.score = self.score + 1 // increment the score
self.score = self.score + 2 // increment the score
Immediately run the project again, and push the button. Sure enough, now the number increases by two every time, in both ScoreBox instances. Notice that you didn’t have to do anything horrible and clumsy like delete the ScoreBox control instances from the window and replace them with new ones; an instance takes on the altered class behavior immediately.
Behind the simplicity of this example lurks the power of inheritance. The ScoreBox instance can receive the Score message because we gave the ScoreBox class a Score property; but it can receive the Text message because the ScoreBox class’s superclass, StaticText, has a Text property. The ScoreBox instance can receive the Increase message because we gave the ScoreBox class an Increase method handler; but it has an Open event handler, which is called automatically by REALbasic, because that’s how REALbasic has defined its superclass, StaticText. Most impressive of all, we have created a working interface element without knowing anything about how to drive the Macintosh Toolbox, simply by subclassing a built-in class that does know how.