Lecture
The role of decomposition
As Dijkstra notes, "The technique of mastering complex systems has been known since ancient times - divide et impera (divide and rule)" [16]. In designing a complex software system it is essential to divide it into ever smaller and smaller subsystems, each of which can be refined independently. In that case we shall not exceed the capacity of the human brain: to understand any level of the system we need to hold in mind at one time information about only a few of its parts (by no means all of them). Indeed, as Parnas observed, decomposition is brought about by the complexity of programming the system, since it is precisely this complexity that compels us to partition the system's state space [17].
Algorithmic decomposition. Most of us have been formally trained in top-down structured design, and we perceive decomposition as the ordinary partitioning of algorithms, where each module of the system performs one of the steps of an overall process. Figure 1-2 gives, as an example, one of the products of structured design: a structure chart that shows the connections among the various functional elements of the system. This structure chart illustrates part of a program that alters the contents of a master file. It was produced automatically from a data flow diagram by a special expert system that knows the rules of structured design [18].
Object-oriented decomposition. Suppose that an alternative way of decomposing this problem exists. In Figure 1-3 we have divided the system, choosing as the criterion of decomposition the membership of its elements in the various abstractions of the given problem domain. Before dividing the problem into steps such as Get formatted update (obtain the changes in formatted form) and Add check sum (add to the check sum), we must identify such objects as Master File (the main file) and Check Sum (the checksum), which are borrowed from the vocabulary of the problem domain.
Although both diagrams solve one and the same problem, they do so in different ways. In the second decomposition the world is represented as a collection of autonomous actors that interact with one another so as to produce behavior of the system corresponding to a higher level. Get formatted update (obtain the changes in formatted form) is no longer present as an independent algorithm; this action now exists as an operation on the object File of Updates (the file of changes). This operation creates another object - Update to Card (the change to the card). Thus each object possesses its own behavior, and each of them models some object of the real world. From this point of view an object is a quite tangible thing that exhibits quite definite behavior. Objects do something, and we can, by sending them a message, ask them to do this or that. Since our decomposition is based on objects rather than on algorithms, we call it object-oriented decomposition.

Figure 1-2. Algorithmic decomposition.
Decomposition: algorithmic or object-oriented? Which decomposition of a complex system is the more correct - by algorithms or by objects? There is a catch in this question, and the correct answer to it is: both aspects are important. Partitioning by algorithms focuses attention on the order in which events occur, while partitioning by objects gives particular importance to the agents that are either the objects or the subjects of action. However, we cannot construct a complex system in two ways at once, all the more so since these ways are essentially orthogonal [Langdon suggests that this orthogonality has been studied since ancient times. He writes: "C. H. Waddington noted that this duality of views can be traced back to the ancient Greeks. The passive view was put forward by Democritus, who asserted that the world is made of atoms. This position of Democritus placed matter at the center of everything. The classic representative of the other side - the active view - was Heraclitus, who emphasized the notion of process"[34]]. We must begin partitioning the system either by algorithms or by objects, and then, using the resulting structure, try to view the problem from the other point of view.
Experience shows that it is more useful to begin with object-oriented decomposition. Such a beginning will help us better cope with bringing organization to the complexity of software systems. Above, this object-oriented approach helped us in describing such dissimilar systems as computers, plants, galaxies, and social institutions. As will be seen later (in Chapters 2 and 7), object-oriented decomposition has several extremely important advantages over algorithmic decomposition. Object-oriented decomposition reduces the size of software systems through the reuse of common mechanisms, which leads to a substantial economy of expression. Object-oriented systems are more flexible and evolve more easily over time, because their designs are based on stable intermediate forms. Indeed, object-oriented decomposition substantially reduces the risk of building a complex software system, since it evolves out of smaller systems in which we are already confident. Moreover, object-oriented decomposition helps us make sense of a complex software system by offering us reasonable decisions about the choice of a subspace of a large state space.
The advantages of object-oriented systems are demonstrated in Chapters 8-12 by examples of application programs from various fields. The following sidebar compares object-oriented design with more traditional approaches.

Figure 1-3. Object-oriented decomposition.
The role of abstraction
Above we referred to Miller's experiments, which established that a person can normally take in only 7Ѓ2 chunks of information at once. This number appears not to depend on the content of the information. As Miller himself remarks: "The span of our memory imposes severe limitations on the amount of information that we are able to receive, process, and remember. By organizing the incoming information simultaneously into several different channels and in the form of a sequence of separate chunks, we can break through... this informational bottleneck" [35]. In modern terminology this is called chunking, or the identification of abstractions.
| Methods of designing software systems We decided it would be useful to distinguish between the notions of method and methodology. A method is a disciplined process for producing models that describe, by well-defined means, various aspects of the software system being developed. A methodology is a collection of methods applied across the software development life cycle and unified by one common philosophical approach. Methods are important for several reasons. First, they bring order to the process of building complex software systems, as common means available to the whole team of developers. Second, they allow managers, in the course of development, to assess the degree of progress and the risk. Methods appeared as a response to the growing complexity of software systems. In the early days of the computer era it was very hard to write a large program, because computers' capabilities were limited. The limitations stemmed from the amount of main memory, the speed of reading information from secondary media (magnetic tapes served as these), and the speed of processors, whose clock cycle was hundreds of microseconds. In the 1960s and 1970s the effectiveness of using computers rose sharply, their prices began to fall, and the capabilities of computing machines increased. As a result it became profitable, and indeed necessary, to build more and more application programs of heightened complexity. High-level algorithmic languages began to be used as the main tools for creating software products. These languages extended the capabilities of individual programmers and of development teams, which, ironically, in turn led to an increase in the level of complexity of software systems. In the 1960s and 1970s many methods were developed to help cope with the growing complexity of programs. The most widespread was top-down structured design. The method was directly based on the topology of traditional high-level languages such as FORTRAN or COBOL. In these languages the fundamental basic unit is the subprogram, and the program as a whole takes the form of a tree in which some subprograms call other subprograms as they execute. Structured design uses precisely this approach: algorithmic decomposition is applied to break a large problem down into smaller ones. At the same time computers of still greater, truly gigantic capabilities began to appear. The significance of the structured approach remained the same, but as Stein remarks, "it turned out that the structured approach does not work if the size of the program exceeds approximately 100,000 lines" [19]. Recently dozens of methods have appeared, in most of which the obvious shortcomings of structured design have been eliminated. The most successful methods were developed by Peters [20], Yau and Tsai [21], and also by Teledyne-Brown Engineering [22]. Most of these methods are variations on the same themes. Sommerville suggests dividing them into three main groups [23]:
The data flow method is best described in the early work of Jackson [31, 32], and also of Warnier and Orr [33]. In this method the software system is regarded as a transformer of input flows into output flows. The data flow method, like the structured method, has been successfully applied to a number of complex problems, in particular in information systems, where there are direct connections between the system's input and output flows and where no particular attention need be paid to performance. Object-oriented design (OOD) is the approach whose foundations are set out in this book. OOD rests on the idea that a software system must be designed as a collection of objects interacting with one another, each object being regarded as an instance of a definite class, with the classes forming a hierarchy. The object-oriented approach reflects the topology of the newest high-level languages, such as Smalltalk, Object Pascal, C++, CLOS, and Ada. |
Wulf describes this process thus: "We humans have developed an exceptionally effective technique for overcoming complexity. We abstract from it. Being unable to reproduce a complex object in full, we simply ignore the not-too-important details and thus deal with a generalized, idealized model of the object" [36]. For example, in studying the process of photosynthesis in plants, we concentrate our attention on the chemical reactions in certain cells of the leaf and pay no attention to the remaining parts - the petioles, the veins, and so on. And although we are still forced to encompass a considerable amount of information at once, thanks to abstraction we deal with chunks of information of substantially greater semantic content. This is especially true when we view the world from an object-oriented point of view, since objects, as abstractions of the real world, constitute distinct, rich, cohesive units of information.
In Chapter 2 the concept of abstraction is examined in greater detail.
The role of hierarchy
Another way of enlarging our chunks of information is to organize hierarchies of classes and objects within the system. The object structure is important, since it illustrates the pattern of interaction of objects with one another, which is carried out by means of mechanisms of interaction. The class structure is no less important: it defines the commonality of structure and behavior within the system. Why, for example, study the photosynthesis of every cell of a particular leaf of a plant, when it is enough to study one such cell, since we expect that all the rest behave in a similar way. And although we regard each object of a certain type as distinct, we may assume that its behavior will be similar to the behavior of other objects of the same type. By classifying objects into groups of related abstractions (for example, types of plant cells as against animal cells), we clearly separate the common and the unique properties of different objects, which then helps us cope with the complexity inherent in them [37].
Identifying hierarchies in a complex software system is not always easy, since it requires developing models of many objects, the behavior of each of which may be extraordinarily complex. However, once they have been identified, the structure of a complex system and, in turn, our understanding of it are at once greatly clarified. Chapter 3 examines in detail the nature of class and object hierarchies, and Chapter 4 describes techniques for recognizing these structures.
Comments