|< Free Open Study >|
2.1 Introduction to Classes and Objects
The object-oriented paradigm uses the concepts of class and object as basic building blocks in the formation of a consistent model for the analysis, design, and implementation of applications. These concepts can best be explained through a real-world example. Given a room full of people, if you were to ask, "How many people in this room could build an alarm clock if given all the pieces?" at best one or two individuals would raise their hand. If the same room of people were asked, "How many people in this room could set an alarm clock to go off at 9 a.m.?" it is a safe bet that most people would raise their hand. Isn't it absurd that so many people claim to be able to use an alarm clock when they can't even build an alarm clock? The immediate response to this question is, "Of course not! Your question is absurd!"
There are many things in the real world that we are capable of using without knowing anything about their implementation: refrigerators, cars, photocopy machines, and computers, just to name a few. The reason they are easy to use without knowledge of their implementation is that they are designed to be used via a well-defined public interface. This interface is heavily dependent on, but hides from its users, the implementation of the device. This design strategy is what allows the alarm clock manufacturer the freedom to replace the 60 tiny components currently being used in the construction of alarm clocks for three subcomponents made overseas without any offense to the users of alarm clocks.
Another example of public interface versus implementation can be seen within the domain of automobiles. Very few users of automobiles cared when car manufacturers went from mechanical ignition systems (i.e., distributor, points, condenser, etc.) to electronic ignition systems. Why? The public interface remained the same; only the implementation changed. Imagine, however, that you go to a car dealer to buy a new car and the dealer hands you a key and tells you to test drive the car. You sit in the driver's seat and look for the key hole of the ignition. You check the steering column, the dashboard, and the immediate area to no avail. You ask the dealer how to start the car, and he or she says, "Oh, with this model you use the key to open the trunk and inside the trunk you'll find a red button. Just push the red button and the car will start." Now you are upset because the car maker modified the public interface you have come to associate with automobiles.
One of the basic ideas in the object-oriented paradigm is exactly this philosophy. All implementation constructs in your system should be hidden from their users behind a well-defined, consistent public interface. Users of the construct need to know about the public interface but are never allowed to see its implementation. This allows the implementor to change the implementation whenever he or she desires, so long as the public interface remains the same. As a frequent traveler, I can assure you that the benefits of being able to use alarm clocks without knowledge of their implementation are great. I have stayed in many hotels using a wide assortment of alarm clocks; electric, windup, battery operated, in both digital or analog models. Not once have I sat on a plane worrying that I wouldn't be able to use the alarm clock in my hotel room.
Most readers immediately understood what I meant by the term "alarm clock" even though there probably wasn't an alarm clock nearby. Why is that? You have seen many alarm clocks in your life and realized that all alarm clocks share certain attributes such as a time, an alarm time (both displayed in terms of hours and minutes), and a designation as to whether the alarm is on or off. You also realize that all alarm clocks you have seen allow you to set their time, set their alarm time, and turn the alarm on and off. In effect, you now have a concept, called alarm clock, which captures the notion of data and behavior of all alarm clocks in one tidy package. This concept is known as a class. The physical alarm clock you hold in your hand is an object (or instance) of the alarm clock class. The relationship between the notion of class and object is called the instantiation relationship. An alarm clock object is said to be instantiated from the alarm clock class, while the alarm clock class is said to be the generalization of all alarm clock objects you have encountered (see Figure 2.1).
If I were to tell you that my alarm clock jumped off my nightstand, bit me, then chased after the neighbor's cat, you would almost certainly consider me mad. If I told you my dog did the same things, it would sound quite reasonable. This is because the name of a class not only implies a set of attributes, it also denotes the behavior of the entity. This bidirectional relationship between data and behavior is a cornerstone of the object-oriented paradigm.
An object will always have four important facets:
To put this discussion in the perspective of software development, a class can be implemented as a record definition with the important addition of the list of operations allowed to work with that record definition. In a procedural language it is easy to find data dependencies on a given function. Simply examine the implementation of the function and look at the data types of all parameters, return values, and local variable declarations. If, however, you want to find the functional dependencies on a data definition, you are required to examine all of the code, looking for functions dependent on your data. In the object-oriented model, both types of dependencies (data to functions and functions to data) are readily available. Objects are variables of a class data type. Their internal details should be visible only to the list of functions associated with their class. This limiting of access to the internal details of objects is called information hiding. It is optional in many object-oriented languages, which leads to our first and most important heuristic.
The violation of this heuristic effectively throws maintenance out the window. The consistent enforcement of information hiding at the design and implementation level is responsible for a large part of the benefits of the object-oriented paradigm. If data is made public, it becomes difficult to determine which portion of the system's functionality is dependent on that data. In fact, the mapping of data modifications to functionality becomes identical to that in the action-oriented world. We are forced to examine all of the functionality in order to determine which is dependent on the public data.
From time to time, a developer will argue, "I need to make this piece of data public because …." In this case, the developer should ask him or herself, "What is it that I'm trying to do with the data and why doesn't the class perform that operation for me?" In all cases the class is simply missing a necessary operation. Consider the File class in Figure 2.2. The developer accidentally thought that the byte_offset data member should be global to allow for random-access I/O, but what was really needed was an operation(s) to perform that task. (Note to non-C programmers: The functions fseek and ftell are standard C library routines for handling random-access file I/O.) Beware of developers who boldly state, "We can make this piece of data public because it will never change!" One of Murphy's laws of programming will see to it that it is the first piece of data that needs to change.
We can further illustrate the benefits of data hiding by considering an example of a point class whose implementation is in rectangular coordinates (see Figure 2.3). The naive designer might argue that we can make the x- and y-coordinates of the point public because that implementation will never change. Inevitably, some new requirement will force a change to polar coordinates, thereby affecting any user of the class point. Had we made the data hidden, then only the implementors of the class would have needed to modify their code.
|< Free Open Study >|