Excerpt from

Booch, Grady, “Object-Oriented  Development,” IEEE Transaction on Software Engineering,” Vol. SE-12, No. 2, Feb. 1986, p. 213-215.

 

OBJECT-ORIENTED DEVELOPMENT

Let us examine the process of object-oriented development more closely. Since we are dealing with a philosophy of design, we should first recognize the fundamental criteria for decomposing a system using object-oriented techniques:

Each module in the system denotes an object or class of objects from the problem space.

            Abstraction and information hiding form the foundation of all object-oriented development [6], [7]. As Shaw reports, “an abstraction is a simplified description, or specification, of a system that emphasizes some of the system’s details or properties while suppressing others” [8]. Information hiding, as first promoted by Parnas, goes on to suggest that we should decompose systems based upon the principle of hiding design decisions about our abstractions [9].

            Abstraction and information hiding are actually quite natural activities. We employ abstraction daily and tend to develop models of reality by identifying the objects and operations that exist at each level of interaction. Thus, when driving a car, we consider the acceleration, gauges, steering wheel, and brake (among other objects) as well as the operations we can perform upon them and the effect of those operations. When repairing an automobile engine, we consider objects at a lower level of abstraction, such as the fuel pump, carburetor, and distributor.

            Similarly, a program that implements a model of reality (as all of them should) may be viewed as a set of objects that interact with one another. We will study the precise nature of objects in the following section, but next, let us examine how object-oriented development proceeds. The major steps in this method are as follows:

·        Identify the objects and their attributes.

·        Identify the operations suffered by and required of each object.

·        Establish the visibility of each object in relation to other objects.

·        Establish the interface of each object.

·        Implement each object.

These steps are evolved from an approach first proposed by Abbott [10].

            The first step, identify the objects and their attributes, involves the recognition of the major actors, agents, and servers in the problem space plus their role in our model of reality. In the cruise-control system, we identified concrete objects such as the accelerator, throttle, and engine and abstract objects such as speed. Typically, the objects we identify in this step derive from the nouns we use in describing the problem space. We may also find that there are several objects of interest that are similar. In such a situation, we should establish a class of objects of which there are many instances. For example, in a multiple-window user interface, we may identify distinct windows (such as a help window, message window, and command window) that share similar characteristics: each object may be considered an instance of some window class.

            The next step, identify the operations suffered by and required of each object, serves to characterize the behavior of each object or class of objects. Here, we establish the static semantics of the object by determining the operations that may be meaningfully performed on the object or by the object. It is also at this time that we establish the dynamic behavior of each object by identifying the constraints upon time or space that must be observed. For example, we might specify that there is a time ordering of operations that must be followed. In the case of multiple-window system, we should permit the operations of open, close, move, and size upon a window object and require that the window be open before any other operation be performed. Similarly, we may constrain the maximum and minimum size of a particular window.

            Clearly, the operations suffered by an object define the activity of an object when acted upon by other objects. Why must we also concern ourselves with the operations required of an object? The answer is that identifying such operations lets us decouple objects from one another. For example, in the multiple-window system we might assume the existence of some terminal object and require the operations of Move _Cursor and Put. As we will see later, languages such as Ada provide a generic mechanism that can express these requirements. The result is that we can derive objects that are inherently reusable because they are not dependent upon any specific objects, but rather depend only upon other classes of objects.

            In the third step, to establish visibility of each object in relation to other objects, we identify the static dependencies among objects and classes of objects (in other words, what objects see and are seen by a given object). The purpose of this step is to capture the topology of objects from our model of reality.

            Next, to establish the interface of each object, we produce a module specification, using some suitable notation (in our case, Ada). This captures the static semantics of each object or class of objects that we established in a previous step. This specification also serves as a contract between the clients of an object and the object itself. Put another way, the interface forms the boundary between the outside view and the inside view of an object.

            The fifth and final step, implement each object, involves choosing a suitable representation for each object or class of objects and implementing the interface from the previous step. This may involve either decomposition or composition. Occasionally an object will be found to consist of several subordinate objects and in this case we repeat our method to further decompose the object. More often, an object will be implemented by composition; the object is implemented by building on top of existing lower-level objects or classes of objects. As a system is prototyped, the developer may chose to defer the implementation of all objects until some later time and just rely upon the specification of the objects (with suitable stubbed implementations) to experiment with the architecture and behavior of a system. Similarly, the developer may chose to try several alternate representations over the life of the object, in order to experiment with the behavior of various implementations.

            We must point out that object-oriented development is a partial-lifecycle method; it focuses upon the design and implementation stages of software development. As Abbott observes, “although the steps we follow in formalizing the strategy may appear mechanical, it is not an automatic procedure…[it] requires a great deal of real world knowledge and intuitive understanding of the problem” [11]. It is therefore necessary to couple object-oriented development with appropriate requirements and analysis methods in order to help create our model of reality. We have found Jackson Structured Development (JSD) to be a promising match [12] and recently, there has been interest in mapping requirement analysis techniques such as SREM to object-oriented development [13].

            Systems designed in an object-oriented manner tend to exhibit characteristics quite different than those designed with more tranditional functional approaches. As Fig. 5 illustrates, large object-oriented systems tend to be built in layers of abstraction, where each layer denotes a collection of objects and classes of objects with restricted visibility to other layers; we call such a collection of objects a subsytem. Furthermore, the components that form a subsystem tend to be structurally flat (like we say in Fig. 4), rather than being strictly hierarchical and deeply nested.

            It is also the case that the global flow of control in an object-oriented system is quite different from that of a functionally decomposed system. In the latter case, there tends to be a single thread of control that follows the hierarchical lines of decomposition. In the case of an object-oriented system, because objects may be independent and autonomous, we typically cannot identify a central thread of control. Rather, there may be many threads active simultaneously throughout a system. This model is actually not a bad one, for it more often reflects our abstraction of reality. We should add that the subprogram call profile of an object-oriented system typically exhibits deeply nested calls; the implementation of an object operation most often involves invoking operations upon other objects.

            There are many benefits to be derived from an object-oriented approach. As Buzzard notes, “there are two major goals in developing object-based software. The first is to reduce the total life-cycle software cost by increasing programmer productivity and reducing maintenance costs. The second goal is to implement software systems that resist both accidental and malicious corruption attempts” [14]. Giving empirical evidence that supports these points, a study by Boehm-Davis notes that “the completeness, complexity, and design time data would seem to suggest that there is an advantage to generating program solutions using … object-oriented methods” {15}. Regarding the maintainability of object-oriented systems, Meyer observers that “apart from its elegance, such modular, object-oriented programming yields software products on which modifications and extensions are much easier to perform than with programs structured in a more conventional, procedure-oriented fashion” [16]. In general, understandability and maintainability are enhanced due to the fact that objects and their related operations are localized.

            Perhaps the most important benefit of developing systems using object-oriented techniques is that this approach gives us a mechanism to formalize our model of reality. As Borgida notes, “the chief advantage of object-oriented frameworks is that they make possible a direct and natural correspondence between the world and its model” [17]. This even applies to problems containing natural concurrency, for as the Boehm-Davis study reports, “the object-oriented method seemed to produce better solutions for [a problem] which involved real-time processing” [18]

           

Figure 5