O'Reilly logo

REALBasic: TDG, 2nd Edition by Matt Neuburg

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Overriding

In the example from Section 4.1, earlier in this chapter, we defined ScoreBox, a subclass of StaticText. Every message that ScoreBox can receive is defined either in itself or in its superclass (or in the superclass of that, and so on). The superclass defines some members; the subclass inherits these and adds some more. But in real life we may wish a subclass to replace the functionality of a member defined in its superclass. This is called overriding . To do this, one simply defines in the subclass a member with the same name as a message that the superclass can receive. Now the message is defined in both the subclass and the superclass.

In making an example, there’s no point using ScoreBox to override any of StaticText’s members. This is basically the same issue discussed in Section 3.6.4: where a built-in class is concerned, to override a property would hide the built-in functionality associated with that property, which is undesirable, and to override a method is forbidden. So we need a superclass and a subclass both of which we ourselves have defined. Let’s subclass ScoreBox to make a DoubleScoreBox class. This will be a class that differs from ScoreBox only in that when it receives the Increase message, it increments its score by 2.

Create the DoubleScoreBox class and make it a subclass of ScoreBox. Clear the Window Editor of controls. Drag in a ScoreBox, select it, and give it a more meaningful name—TheScoreBox. Drag in a DoubleScoreBox and name it TheDoubleScoreBox. Drag in a PushButton and have its Action event handler go like this:

theScoreBox.increase
theDoubleScoreBox.increase

Now it’s time to define DoubleScoreBox’s Increase method, to override ScoreBox’s Increase method. Open DoubleScoreBox’s Code Editor and define a method handler Increase, with this code:

Sub increase()
    self.score = self.score + 2 // increment the score
    self.showScore // display the score
End Sub

Run the project and press the button repeatedly. It works. Let’s talk about what’s happening.

It’s true that our instance TheDoubleScoreBox is a ScoreBox, which defines an Increase method, to increment by 1; but its final class is DoubleScoreBox, which also defines an Increase method, to increment by 2, and that’s the one that gets called when we send the Increase message to TheDoubleScoreBox. On the other hand, when TheDoubleScoreBox’s Increase method calls self.showScore, DoubleScoreBox has no ShowScore method; but the message is acceptable, because a DoubleScoreBox is also a ScoreBox, and a ScoreBox does define a ShowScore method, which is what gets called. We may summarize by saying that message names are resolved upward through the instance’s class and its superclasses: first we look in the final class of the instance to see if it accepts the message; only if not do we look at its superclass, and so on. This is illustrated in Figure 4-2.

Overriding

Figure 4-2. Overriding

Our use of the phrase “the instance” may seem surprising, because in the previous section the important thing was the declared datatype of the reference. To understand what’s happening, separate the message-sending process into two distinct stages. First, REALbasic decides whether the reference can be sent the specified message at all; this is done by resolving the message name upward from the reference’s declared datatype. If it can, then REALbasic decides which class should actually receive the message; this is done by resolving the message name upward from the instance’s actual final class. For example:

dim s as scorebox
s = theScoreBox
s.increase
s = theDoubleScoreBox
s.increase

This increases TheScoreBox’s score by 1 and TheDoubleScoreBox’s score by 2, even though the reference both times is s, which is declared as a ScoreBox. Do you see why? The fact that the reference s is declared as a ScoreBox means that it can accept the Increase message, so this code is legal—and that’s all it means. Now we come to the question of what this code will actually do; and that depends upon the instance that s points to. When s points to an instance of the ScoreBox class, it is ScoreBox’s Increase method that is called. When s points to an instance of the DoubleScoreBox class, it is DoubleScoreBox’s Increase method that is called.

The principle here is that all programmer-defined methods are virtual methods . That’s just technical talk for the very thing we’ve just been saying: the class of a reference may make it a legal recipient of a message, because that class can handle that message; but that fact is no guide as to what class will actually handle the message, because the instance may be of a class that overrides it.

Let’s look at the matter from a different perspective. Instead of a linear architecture, imagine a situation where one class has two subclasses. We’ll take advantage of overriding a virtual method so that it becomes, in effect, a decision-making mechanism. Suppose we’re writing a Tic-Tac-Toe game. Every game piece is either an X or an O. But all game pieces have some common behavior. For example, a game piece should know how to draw itself in a given square on the board. Clearly we’re going to have a GamePiece class and a Square class. We envision a routine that takes a piece and a square and tells that piece to draw itself into that square, like this:

Sub drawPieceIntoSquare(p as gamePiece, s as square)
    p.drawYourselfInto s
End Sub

The exact behavior of p is going to depend on whether it is an X or an O. What will it mean, to be an X or an O? Let’s make it mean that there is an X class and an O class! Obviously, these are both subclasses of GamePiece. Presume that p is either an X instance or an O instance. If the X class has a DrawYourselfInto method and the O class has a DrawYourselfInto method, then when we send p the DrawYourselfInto message, the right thing will happen automatically. If p is an X instance, X’s DrawYourselfInto will execute; if p is an O instance, O’s DrawYourselfInto will execute. What handler will execute depends upon what class this particular instance is during this particular call to our routine. So the class structure becomes a decision-making mechanism! The fact that we can send a message to one higher class as a way of choosing among several lower classes is called polymorphism .

However, we’ve left out a small piece of the puzzle. We define GamePiece. We define X as a subclass of GamePiece, and we give it a DrawYourselfInto method. We define Y as a subclass of GamePiece, and we give it a DrawYourselfInto method. We try to execute the previous code, and we get an error. What’s happened?

The answer is that we forgot the first part of the message-sending process. We know that the DrawYourselfInto message will get routed to the right class, the class of the actual instance that p points to. But we also have to make it legal to send the DrawYourselfInto message to p in the first place! This means that GamePiece must also have a DrawYourselfInto method handler. In other words, in order to take advantage of a virtual method, there has to be a virtual method. You can’t override something that isn’t there in the first place.

Very well; but what should GamePiece’s DrawYourselfInto handler do? It’s perfectly possible that it will do nothing at all; it might be completely empty! It could be that the whole of X’s drawing functionality is contained in its DrawYourselfInto handler, and the whole of O’s drawing functionality is contained in its DrawYourselfInto handler. And every GamePiece is going to be an X or an O. Our routine is never going to receive a value for p whose final class is GamePiece; it will always be an X or an O. So GamePiece’s DrawYourselfInto handler does nothing, because it will never be called. Is this silly? Not at all. In fact, it’s such a common technique that it has a name: GamePiece’s DrawYourselfInto handler is said to be abstract . It exists only so that X and O can override it, so that a reference declared as GamePiece can be sent the DrawYourselfInto message, taking advantage of polymorphism.

In fact, we can go further. It may be that no instance whose final class is GamePiece will ever be generated throughout the entirety of our program. Not just GamePiece’s DrawYourselfInto handler, but the whole GamePiece class, might exist only so that X and O can subclass it and take advantage of polymorphism. Again, this is quite common, and GamePiece is then said to be an abstract class . (See the Appendix A for a Tic-Tac-Toe game that employs this sort of architecture.)

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required