• Aucun résultat trouvé

Why Objects?

Dans le document PROGRAMMING PENPOINT (Page 29-35)

The two most important benefits of using objects in PenPoint are increased productivity and improved software quality. For example, whenever you reuse an object generated by a class in PenPoint, not only are you reusing predefined code, thereby saving development time and storage space, but you are reaping the benefits of GO Corporation's qual-ity assurance program and its commitment to having their objects work well with each other-all for free.

In general, object-based environments are classified based on their implementation of various forms of abstractions. For example, you have probably heard of various programming languages such as Smalltalk, Objective-C, and C++ that support objects, and you might even have used one or more of them. You might also have heard the terms "encapsula-tion," "inheritance," and dynamic or static "binding." Because there are many different uses of these terms in the field right now, I think it's pru-dent to spend a few moments outlining the basic concepts so we can work with a consistent and common vocabulary.

Encapsulation

The term encapsulation describes the way in which the behavior and per-sistent data needed to implement a model of a real world abstraction is organized. By definition, only the behavior defined for the particular abstraction may access that entity's data. This produces a form of "fire-wall" protection by not allowing outsiders to interfere with the entity's internal workings. In essence, you use encapsulation to provide a data abstraction of the real world entity you wish to model.

For example, suppose you want to describe a light fixture that can be either on or off. You might say that in order for an object to be a light fix-ture, it must respond to being turned on or off, and it must remember whether or not it is on or off. The persistent data would be a variable that tracked whether or not a light fixture is on. The behavior would include ways to turn the fixture on and off and possibly a way to check the state that the light fixture is in.

The information used to keep track of an object is often organized as a set of variables called instance variables. Behaviors that manipulate this data are then called instance methods. Because the use of objects means data abstraction, only the instance methods of an object can directly access the instance variables of that object. Objects that have the same kind of private data and share the same instance methods belong to the

same class of objects. Here's a more formal definition of what the light fix-ture object might be:

Class

LightFixture Instance Variables

BOOL onOffFlag Instance Methods

turnFixtureOn turnFixtureOff isFixtureOn isFixtureOFF

What's nice about this formal definition is how easy it is to see that all light fixtures share the same behavior and same type of stateful informa-tion. What differs is that the information for each individual light fixture object is unique. This observation leads to the concept of each object hav-ing a unique part and a shared part. In general, the unique part of an object contains the instance data and a pointer to the shared information (generally the instance methods) for the kind of object it is.

Figure 2.1 shows the conceptual layout of several light fixture objects.

Notice that although there are two different objects, both share a single copy of the instance methods.

One last point-it's important to realize that there are two fundamen-tally different views of the same object, based on whether you're the pro-vider (sometimes called the producer) or the user (sometimes called the consumer) of the object. The producer of an object is the person who designs, develops, and produces the object that the consumer uses. The producer has access to the instance data of the object, while the consumer can only access the abstraction through the methods the producer pro-vides. The consumer, on the other hand, should be able to reuse a produc-er's objects free of worry that the underlying abstraction is faulty. This means that the most productive programmers in an object-based environ-ment are those who spend the bulk of their time as consumers of pre-existing code.

FIGURE 2.1 How Light Fixture Objects Are Laid Out

Inheritance

turnFixtureOn tu rn Fixtu reOff

Mth Table

There are times when you, as a consumer of objects, can find existing objects that almost match most of your specifications. At that point, you would like to be able to extend the existing object by adding the behavior and data needed to accomplish your task. In object-oriented program-ming, you accomplish this using a technique called inheritance.

Inheritance is a formal methodology for reusing large pieces of code by adding a new piece of code that defines only the differences between old and new. For example, you could produce a new type of light fixture, say a timed light fixture, by adding timing capabilities to an existing light fix-ture. The terminology used to represent this relationship is that the new class (TimedLightFixture) is a subclass of the pre-existing (LightFixture) class. Conversely, you would say that the pre-existing class (LightFixture) is the superclass of the new (TimedLightFixture) class. Here's a list that shows the extensions necessary to build a new class of timed light fixture objects.

Class

TimedLightFixture SuperClass

LightFixture Instance Variables

BOOL timerOnOff

TIME turnOnAt, turnOff At.

Instance Methods turnFixtureOn turnFixtureOff

setOnTime (TIME newOnTime) setOffTime (TIME newOffTime)

In addition to adding new instance data and methods, the new TimedLightFixture class redefines, or overrides, two methods (turnFix-tureOn, turnFixtureOff) defined in its superclass. If you send the turnFix-tureOn message to a light fixture object, that request will be handled by the method in the LightFixture class. On the other hand, the same request to a timed light fixture object will be handled in the TimedLightFixture class because that class has its own turnFixtureOn method defined. As in most object-oriented programming environments, PenPoint supports sev-eral ways for the subclass to access the behavior of a superclass's method that it overrides.

As I mentioned earlier, there are two views of every object: the produc-er's and the consumproduc-er's. The previous list defined the producproduc-er's view of the new TimedLightFixture class. It simply shows the differences between a timed light fixture and a basic light fixture. The next list defines the con-sumer's view of a timed light fixture, including the data and methods inherited from a light fixture.

Class

TimedLightFixture Instance Variables

BOOL onOffFlag BOOL timerOnOff

TIME turnOnAt, turnOff At.

Instance Methods isFixtureOn

isFixtureOFF turnFixtureOn turnFixtureOff

setOnTime (TIME newOnTime) setOfffime (TIME newOfffime)

The inheritance hierarchy for this example is very simple and therefore fairly easy to explain. As you can imagine, deep inheritance hierarchies can quickly lead to very complicated objects and problems in trying to figure out exactly where something is happening. Inheritance is not a panacea and should be used as an implementation tool only. When designing objects, you should start with their requirements and then try to find an existing object that does the trick. Then if you can't find a direct match, you should consider using inheritance.

Binding Options

You work with objects by sending them messages. These messages are then translated to an appropriate method in a particular class, based on the type of object you sent the message to. For example, if you send the turnFixtureOn message to a timed light fixture object, it uses the method found in the TimedLightFixture class to carry out your request. On the other hand, if you sent the same message to a light fixture object, it uses the method found in the LightFixture class to carry out your request. The process of determining which method in which class should respond to which message sent to a certain object is known as binding.

Binding a requested behavior (a message send) to the actual implemen-tation (the method) can take place at multiple points in the life cycle of an application. For example, you can define a macro that provides a set of functionality that is expanded during preprocessing. The macro is said to be bound at preprocessing time, because once the preprocessor has run, the behavior of that macro can't be changed. Another example of a bind-ing tool is a linker, which resolves references to functions that might be located in different modules. This form of binding is exploited for reuse by defining libraries of functions that multiple applications can share.

The binding of messages sent to objects can occur at various times. For example, you can ask the linker to bind a message sent to a particular class of object to the actual method that will handle the request, but only if you know that information ahead of time. Although this produces efficient code, it severely limits reuse of objects, because the compiler must know every possible use of the object when it's being built. At the other end of the spectrum, you can defer binding until the message is actually sent to the

object in question. At that time, a lookup is performed to see which method to call to access the required functionality. This allows you to build generic objects that will work with any other object, as long as that other object responds to the messages your original object expects to send.

Reliability, Reusability, and Cost

During any design process, there is always the possibility that a new requirement will arise that has no existing solution. When this situation occurs in object-based design, it is usually solved by implementing a new class. Unfortunately, most of the time the solution doesn't go far enough.

A reusable component will go through three distinct stages. The first stage is when the component is good enough for me, its designer, and developer. Because I'm the only one using it, I can make assumptions about its internal representation and limit the amount of destructive test-ing (exceedtest-ing boundary limits, for example) performed. Also, because of the limited requirements on its robustness, I can quickly get a class to this stage. The downside of stopping at this stage is that the cost to develop the component can be spread over one person only, since no one else would want to reuse such a component.

The second stage occurs when you convince me to share my custom component with you. In the process of using the component within a sec-ond application, I will spend additional time uncovering and fixing prob-lems that didn't arise in my application, but manifested themselves in yours. Also, since we are sharing the same object, I'll have the added ben-efit of increased generality in my component. The actual effort in moving from stage one to stage two is usually a fraction of the time taken to get to stage one. However, now I can divide the cost of developing and main-taining the component by two.

Finally, there is stage three, when a component has been designed and tested to be as generic as possible to work in applications as yet unimag-ined. This type of reusability is available in PenPoint as a direct result of PenPoint's use of deferred binding in sending messages between objects.

Getting a class to stage three tends to be fairly expensive, because now I must incur the cost of documenting the component for a much wider audience. However, the cost of developing the component can now be spread over a much larger number of users, both inside and possibly out-side of my development organization.

Dans le document PROGRAMMING PENPOINT (Page 29-35)