Read an Excerpt
Chapter 3: Objects, Classes, and InstancesIn this chapter:
Messages and Dot Notation
Object Design Philosophy
Classes and Instances
Anatomy of a Class
An Instance Is Born
Referring to Instances
The Truth About Controls
Control Clones and Control Arrays
Being Careful with Instance References
Destruction of Instances
Consider programming as a way of thinking, and code as a way of speaking--a language. The programmer thinks about what the program is supposed to do, and then expresses this in code. An object is a programming construct which assists the programmer both in thinking and in speaking about the program, both in organizing and in coding. The program works as if it consisted of autonomous bundles of functionality--the objects. These objects are somewhat like the bundles of reality, the "things," the "objects," in terms of which we perceive and describe the natural world. Thus, the use of objects in programming makes the programmer's thought and speech about the program more natural, which in turn makes programming easier.
What are the objects of which a program is constructed? In general, that's up to the programmer, who, rather like some divinity creating a small universe, dictates both what objects should populate that universe ("Let there be light; let there be the sun; let there be the earth") and what those objects should do and how they should interact ("Let the sun make light to shine upon the earth"). Such power may seem daunting, almost paralyzing, rather than helpful. Where to begin? If there were an easy answer, there wouldn't be approximately six gazillion books about designing object-oriented programs.
REALbasic both enforces object-oriented programming and helps you with object-oriented design. Before you write a line of code, the construction of your program's universe has already been started for you. The program you are contemplating is to be an application; REALbasic is an application framework, and has stocked your program beforehand with objects that mirror the physical elements of an application's interface: windows, buttons, text fields, menu items, and so forth. Such physical elements of the interface are the objects of the "natural world" the user perceives when working with your application, and so they are the very objects in terms of which it is most natural to think and speak about the operation of your program ("This button should react to being pressed by opening that window").
REALbasic's built-in objects are useful and powerful in three ways:
- Conceptually, they organize your program into bundles that reflect the structure of its interface.
- Physically, in the IDE, they act (through their Code Editors) as separate repositories of code.
- Functionally, they supply the programming power of REALbasic's application framework; in other words, not only do they reflect the interface through their structure, they also implement the interface through their hidden programming power, driving the computer and creating physical counterparts for themselves on the screen. For example, windows know how to open and how to draw themselves, buttons know how to be pressed, text fields know how to accept typing, menu items know how to be chosen.
REALbasic's built-in objects may be the only objects your program needs; if your program is fairly simple, that's quite probable. But if your program is more specialized or more complex, you will probably wish to extend REALbasic's objects by modifying them or adding new ones of your own. Either way, you will not be entirely freed from having to make intelligent object-oriented design decisions. At the very least, you will have to construct your program's interface, and you will have to decide how to allocate your code among the objects that implement that interface. To do this efficiently, in a way that makes the program do what you want it to do and as easy as possible to understand, maintain, and alter, you need to draw from two kinds of knowledge: you should understand REALbasic's internal object model and what the built-in objects do; and you should adhere to principles of good object-oriented design.
If this sounds daunting, don't worry; this book is here to help! Throughout this chapter and the rest of , I'll be describing the REALbasic object model, explaining how you work with objects in general and how they fit together to provide the architecture of your application as a whole. The rest of the book provides the details on each of REALbasic's built-in objects.
Messages and Dot Notation
As a programming construct, objects divide a program into pieces. The glue that unites those pieces to form a single working program is the ability of the objects to send one another commands and ask one another questions. The objects are autonomous, but they can communicate; this communication takes place by means of messages. In REALbasic code, the sending of a message to an object is expressed by dot notation.
Suppose, for example, that we have an object called MyWindow and we wish to tell it to turn blue. We could do this by sending it a TurnBlue message--assuming, of course, that the MyWindow object can accept a TurnBlue message. If it can, then we might say:
This shows the basic dot notation syntax whereby messages are sent: the name of the object is followed by a dot, which is followed by the name of the message.
But how does this syntax fit into the larger syntax of the REALbasic language? The answer is that every message is one of two types, both of which are already familiar to us from Chapter 2: it operates, depending on the particular message, either as a subroutine call or as a variable name. As a matter of nomenclature, it happens that the subroutines that can be called by sending a message to an object are called methods, and the variable-like things whose values can be accessed by sending a message to an object are called properties. But syntactically and conceptually, they present nothing new. Since we already know how to use subroutine calls and variable names in code, we already know how to use messages as well.
To illustrate, suppose a particular message designates a method. That means the message operates as a subroutine call. As we know, subroutines can be either procedures or functions, so let's suppose this method, TurnBlue, is a procedure. Then, if that procedure takes no parameters, we might say:
If the procedure takes one parameter (perhaps an integer telling how blue the window should turn), we might say:
Now let's suppose we have a different method, HowBlue, which is a function taking no parameters (perhaps returning an integer telling how blue the window is). We might say:
dim yourBlueness as integer
yourBlueness = myWindow.howBlue()
All the other forms of syntax may also be used, as appropriate to a procedure or a function, taking the appropriate number of parameters.
Now, suppose a particular message designates a property. That means the message operates as a variable name. For example, the window might have a property MaxBlueness which is an integer, denoting the maximum blueness this window should allow itself to adopt. Then we might say:
myWindow.maxBlueness = 40
dim theMax as integer
theMax = myWindow.maxBlueness
myWindow.maxBlueness = myWindow.maxBlueness + 10
Thus, aside from dot notation itself, no new syntax is required to send a message to an object, beyond what was already described in Chapter 2.
REALbasic itself also comes with a large number of built-in procedures and functions apparently detached from any object; for example, in earlier chapters, we have already had cause to mention the MsgBox procedure, the Beep procedure, and the Str function. To call these, as we have seen, you just use their names; no dot notation is required. For the sake of completeness, though, and for a rigorous understanding of their ontological status, you might like to consider such built-in procedures and functions to be methods of a sort of supreme, ultimate, universal object--the global object.
A thing is global if it is available from everywhere without specifying an object that it belongs to; so, for example, since any code whatever can call the Beep procedure without using dot notation, the Beep procedure is global. So, we can pretend there is a universal object, which we might term REALbasicItself, and that when we say:
it is really a kind of shorthand for:
REALbasicItself.beep // you can't actually say this!
This construct is only a pretense; there actually is no REALbasicItself object, and you can't actually send a message to it as in the second example. But there is a sense in which this pretense is accurate, and helps explain the status of REALbasic's built-in procedures and functions: when you call such a procedure or function, you're actually sending a message to REALbasic itself, and accessing one of its methods.
Object Design Philosophy
Methods and properties correspond to the two primary purposes of objects as programming constructs: encapsulation of functionality and maintenance of state. These two purposes underlie the design of object-oriented code, and an understanding of them will help you to make sense of REALbasic's built-in objects, to appreciate REALbasic's application architecture, to organize your code intelligently, and to know when to create your own objects.
Encapsulation of Functionality
The idea behind encapsulation of functionality is that each object should be the repository of knowledge about how to do all the things appropriate to itself.
Consider, for example, a shoot-'em-up arcade game, where every time the user hits a target, the target explodes, and the score, displayed in a box, increases. Now, we could write such a program without using object orientation at all; it's all just pixels on a screen, after all, and code that controls pixels has the same effect no matter how it's organized. But if we do use object orientation, the very language in which we describe the program, where the nouns are "target" and "score" and the verbs are "explodes" and "increases," suggests that the target and the box containing the score are two different objects, and that it is the target that should contain the code for exploding, and the scorebox that should contain the code for increasing. The target "knows" how to explode; the scorebox "knows" how to increase. In other words, the target object has an Explode method, and the scorebox object has an Increase method.
Messages can be sent to the appropriate objects to instigate the appropriate actions. Instead of exploding the target from elsewhere, we send the target the Explode message, as if to say: explode yourself! Instead of increasing the score from elsewhere, we send the scorebox the Increase message, as if to say: increase yourself! (In fact, our use of the conjunction "and" in describing the program's behavior suggests that it should probably be the target which, as it explodes, tells the scorebox to increase.)
When code is object-oriented, and when the objects encapsulate their appropriate functionality, the objects become more like objects in the real world. It is encapsulation of functionality that gives objects their autonomy. Only the target needs to know how to explode; all other objects can remain blissfully ignorant of the details, secure in the knowledge that they can just say
target.explode and all the right things will happen. Moreover, as we develop the program, we can change what an explosion consists of (we can decide to add a sound, for instance) without affecting any code in any other objects; they still just say
target.explode. Indeed, the target object may be so autonomous as to have virtually no dependency on the rest of the program; everything the target needs in order to function is inside itself. This makes it easier to write and maintain not only this program, but also any other programs that may need targets; to implement targets in another program, we have only to copy into that program our target object code from this one.
Maintenance of State
The idea behind maintenance of state is that a value needing to be preserved over time should be preserved as part of the object that chiefly operates on it.
Recall that our arcade game is to have a scorebox that knows how to increase itself, and observe that this implies there is a score. Even while the user is busy missing targets, or taking time out to answer the telephone, some object is remembering this score. It makes sense that this object should be the scorebox itself; the jobs of increasing the score, displaying the score, and knowing the score are naturally related. In other words, the scorebox object has a Score property.
Once again, this approach allows other objects to be ignorant of the details, and allows the scorebox to maintain autonomy. When the user hits a target, the score should increase. Let's say that when the user hits a different object (a bad guy), the score should also increase. Both the target object and the bad guy object react to being hit by telling the scorebox to increase itself:
scorebox.increase. Neither has to worry about what this means, or what the score is, or how the user will be shown the score. The scorebox, in its turn, doesn't have to worry about who is telling it to increase the score. It just sits there, remembering the score, and when it's told to increase it, it does so, and it both remembers and displays the new score, which is simply the value of its Score property.
To be sure, this example is particularly clear-cut in a way that is sometimes not the case. Interesting questions of philosophy and implementation often arise. That's part of the fun, and the challenge, of designing an object-oriented program.
For example, the target object and the bad guy object don't need to know the score or manipulate it themselves; but what if they did? Should they be permitted to access the scorebox's Score property directly, or should they be required to call one of the scorebox's methods? In other words, is the scorebox not just this value's container, but also its owner and, in some sense, its protector?
Furthermore, it may not always be entirely obvious which is "the object that chiefly operates" upon a certain value. It often seems that direct access to a value needs to be shared among several different objects; should one of these be its container, or its owner and protector, or should the value be spun off into a different object entirely?
Then there is the problem of a value's life cycle. Clearly, if the scorebox object for some reason goes out of existence, yet we still need access to the score, the scorebox was an inappropriate choice of container for the score. At the other extreme, we could make the score permanently and publicly accessible and part of no object at all,1 but this seems to defeat the purpose of object-orientation, if there is any object that seems naturally to be the score's owner.
These are all practical and philosophical design decisions with which the programmer must constantly grapple....