Lecture
Warning. Seeing that object technology is now widely known and fairly widespread, some readers may think that the battle has already been won and that there is no further need for its logical justification. That would be a mistake: if we want to avoid common errors and pitfalls, we need to understand the foundations of the method. In fact, one can often see the adjective "object-oriented" (like the adjective "structured" in the preceding era) used simply as a new label for the most traditional software development methods. Only by carefully building the edifice of object technology can one learn to recognize cases of misuse of this fashionable word and avoid the mistakes discussed later in this lecture.
When searching for the correct software architecture, the critical question is that of modularization: what criteria should be used in singling out the modules of our programs?
To answer this correctly, we need to compare the competing candidates.
The basic triangle
Three forces come into play when we use a program to perform some computation
To execute a software system means to use certain processors to apply certain actions to certain objects.

Fig. 5.1. The three forces of computation
Processors are computing devices (physical or virtual) that execute instructions. A processor may be an actual processing unit (for example, a computer's CPU), a process of a conventional operating system, or one of its "threads" in a multithreaded OS.
Actions are operations that produce computation. The exact form of the actions we consider depends on the level of detail of the analysis. For example, at the hardware level actions are machine-language operations, at the hardware-software level they are statements of a programming language, while at the software-system level each major step of a complex algorithm can be regarded as an action.
Objects are the data structures to which actions are applied. Some of these objects — data structures built by the computation for its own purposes — are internal and exist only during the computation, while others (contained in files, databases and other persistent storage) are external and may survive the computations in which they are used.
Processors become important when discussing parallel computation, in which several subcomputations may run simultaneously. In this lecture we restrict ourselves to non-parallel, or sequential, computation, carried out by a single processor (which remains outside the scope of our discussion).
What remain, then, are actions and objects. The duality between actions and objects — what the system does, and what it does it with — is a popular theme in software development.
[x]. A note on terminology. For each of these two aspects there are corresponding synonyms: the word data will be used as a synonym for the word objects, while instead of the word action we, following common practice, will speak of the system's functions.
[x]. The term "function" is likewise not without its drawbacks, since in discussions of software it is used in at least two senses: the mathematical one, and the software sense of a subroutine that returns some result. However, without fear of ambiguity, we will use the phrase "system functions" as required here.
[x]. The reason we use this word rather than "action" is purely a matter of grammatical convenience, from being able to use the corresponding adjective, for example in the phrase "functional decomposition". The word "action" has no corresponding derived adjective. Another term whose meaning in our discussion is equivalent to that of the word "action" is the word "operation".
Any discussion connected with programming must take both aspects into account, object and function, and this holds for the design of a software system as well. But there is one question in answering which we must choose between them — and that is the question of this lecture: what is the criterion for singling out the modules of a system? Here we must decide whether modules will be built as units of functional decomposition or whether they will be built around the principal types of objects.
The answer to this question demonstrates the difference between the OO approach and other methods. In traditional approaches, each module is built around some unit of functional decomposition — some part of an action. Unlike them, the OO method builds each module around some type of objects.
It is not hard to guess that it is precisely this approach that is developed in this book. But we should not accept OO decomposition on faith merely because it is implied by the title of this book, or because it is a "thing-in-itself" that simply must be done.
In the following sections we will analyze the arguments justifying the use of object types as the basis for modularization, beginning with an examination of the merits and limitations of traditional non-OO methods. We will then try to gain a clear picture of what the word "object" actually means for software design, although a full answer, requiring some additional theoretical discussion, will only appear in the next lecture.
We must also postpone to the next lecture the settling of the old and formidable battle that has become the topic of our discussion — the War of Objects and Functions. For now we will prepare a campaign to discredit functions as a basis for decomposition, and, correspondingly, to extol objects for achieving these goals. And yet we should not forget the observation made above: ultimately, in our solutions to problems there must be room for both objects and functions, although not on an equal footing. To establish a new world order, we must define the roles of first- and second-class citizens.
We will first examine the merits and limitations of the traditional approach that uses functions as the basis for the architecture of software systems. This will not only lead us to understand why something more is needed — object technology — but will also help us avoid certain methodological pitfalls, such as premature ordering of operations, of which even experienced OO software developers are known to be guilty.
The key problem in answering the question "around what should systems be structured: around functions or around data?" is the problem of extendibility — more precisely, the goal named continuity in the preceding discussion. As you recall, a design method satisfies this criterion if it leads to a stable architecture ensuring that the extent of change in the design is proportionate to the extent of change in the specification.
Ensuring continuity is the chief concern when considering the real life cycle of software systems, which includes not only the production of an acceptable initial version but also the evolution of the system over a long period of time. Most systems undergo numerous changes after their initial delivery. Hence any model of software development that considers only the period preceding that delivery, and ignores the subsequent era of changes and revisions, is far removed from real life — like those novels that end with the hero marrying the heroine at the very moment when, as everyone knows, the most interesting part is only about to begin.
To assess the quality of an architecture (and of the method that produced it), one must understand not only how simple it was to obtain that architecture in the first place, but, no less importantly, how easy it is to change.
The traditional answer to this question was top-down functional decomposition, briefly defined in one of the previous lectures. How well does top-down design meet the requirements of modularity?
There was also a most ingenious architect who had contrived a new method for building houses, by beginning at the roof, and working downwards to the foundation. He justified this method to me by the practice of two prudent insects, the bee and the spider.
Jonathan Swift, "Gulliver's Travels"
In the top-down approach, a system is built through successive refinements. This process begins with the most general statement of its abstract function, such as
[C0]
"Translate a C program into machine code"
or
[P0]
"Process a user command"
and continues through successive refinement steps. At each step, the level of abstraction of the resulting elements must decrease: each operation at that level is decomposed into a composition of one or more simpler operations. For example, the next step in the first example (a C compiler) might lead to the decomposition
[C1] "Read the program and produce a sequence of tokens" "Parse the sequence of tokens and build an abstract syntax tree" "Annotate the tree with semantic information" "Generate code from the resulting tree" or, using a different structure (and making the simplifying assumption that a C program is a sequence of function definitions): [C'1] from "Initialize the data structures" until "All function definitions have been processed" loop "Read the definition of the next function" "Generate partial code" end "Fill in the cross-references"
In any case, at each step the developer must examine the elements that remain not fully refined (such as "Read the program..." and "All function definitions have been processed") and expand them, using the same refinement process, until everything is at a sufficiently low level of abstraction to allow direct implementation.
The top-down refinement process can be represented as building a tree. The nodes represent the elements of the decomposition, and the branches show the relation "B is a refinement of A".

Fig. 5.2. Top-down development: tree structure
The top-down design method has a number of merits. It is logical, it organizes the discipline of thinking well, it lends itself to effective teaching, it encourages the systematic design of systems, and it helps the developer find ways to overcome the great complexities that usually arise at the initial stage of system development.
The top-down approach can be very useful in developing individual algorithms. However, it has a number of limitations that make its use questionable when designing entire systems:
[x]. The very idea of characterizing an entire system by means of a single function is questionable.
[x]. By using, as the basis for decomposing a system into modules, properties that tend to undergo the greatest change, this method fails to account for the evolutionary nature of software systems.
As a system evolves, what was initially perceived as its main function may become less important over time.
Consider a typical payroll system. When formulating the initial requirements, the customer may have envisioned only what follows from its name: a system for generating paychecks from the corresponding data. His conception of the system, explicit or implicit, might have been a version of the following scheme, perhaps a bit more ambitious:

Fig. 5.3. Structure of a simple payroll system
This system receives some input data (such as an employee's working hours and some information about them) and produces some output (paychecks, and so on). This is a simple functional specification, in the strict sense of the word "functional". It defines the program as a mechanism for performing a single function — paying employees' salaries. The top-down functional design method is intended precisely for such tightly bounded problems, where the task consists in computing a single function — the "top" of the system being constructed.
Suppose, however, that the development of our payroll system has been successfully completed and the program performs all the necessary work. In all likelihood, development will not stop there. Good systems have the annoying habit of inspiring their users with all sorts of ideas about other things they could do. As the system's developer, you were told at the outset that all you had to do was generate paychecks and a couple of auxiliary outputs. But then requests for extensions start landing on your desk one after another: "Can the program collect some additional statistics?" "I told you that next quarter we're going to start paying some employees monthly and others twice a month, didn't I?" "And, by the way, I need a monthly summary report for management, and another quarterly one for the shareholders." "The accountants need a separate report for tax computation." "By the way, are you storing the salary information correctly? We'd really like to give staff interactive access to it. I don't see why it would be hard to add such a feature?"
This phenomenon — the desire to add functions to successful systems that were not planned for in advance — occurs in every application area. A nuclear physics program that initially just applies some algorithm to produce a table of numbers from batch input will inevitably be extended over time. It will have to handle graphical input, produce graphical output, and store the resulting data in a database. A compiler intended only to translate correct source text into object code will, after some time, be substantially extended to pretty-print programs, and to serve as a syntax verifier, a static analyzer, and even a programming environment.
The process of change happens continuously. The new system is still, in many respects, "the same" as the old one: still a payroll system, a nuclear physics program, a compiler. But the original "main function", which at first seemed the most important, often becomes just one of the system's functions, and sometimes disappears altogether, becoming unnecessary.
If analysis and design use a decomposition method based on function, then the structure of the system will follow from the developers' initial understanding of the system's main function. In that case, the addition of any new function, even one that seems simple to the customer, can destroy the entire structure of the system. It is therefore very important to find, as the criterion for decomposition, properties that are less changeable than the system's main function.
The top-down design method assumes that every system is characterized, at its most abstract level, by its main function. Although many textbook examples of algorithmic problems — the "Towers of Hanoi", the "Eight Queens Problem", and so on — can indeed easily be specified by means of their "top" functions, it is more useful to describe practical systems in terms of the services they provide.
Consider some operating system. It is most sensible to represent it as a system providing services such as processor time allocation, memory management, handling of input-output devices, and decoding and executing user commands. The modules of a well-structured OS tend to be grouped around these groups of functions. But that is not the structure one obtains from top-down functional decomposition. This method forces the designer to answer an artificial question — "what is the 'top' function?" — and then to use successive refinements of the resulting answer as the basis for the structure of the system. With a certain amount of effort, one can arrive at the following answer to the original question
"Process all user requests", which can then be refined roughly as follows:
from system startup until shutdown or fatal failure loop "Read a user request and place it in the input queue" "Take a request r from the input queue" "Process r" "Place the result in the output queue" "Take a result q from the output queue" "Deliver the result q to the recipient" end
Refinement could go on. However, it is unlikely that, after such a beginning, anyone would succeed in designing a sensibly structured operating system.
Let us return to the compiler example. Reducing it to its bare essence, or presenting the view of the older textbooks, one could say that a compiler is the implementation of an input-output function transforming the text of a source program in some programming language into machine code for some platform. But for modern compilers this view is inadequate. Among the many services provided by a compiler are error detection, program formatting, the ability to control the system's configuration, logging in to the system, and report generation.
It appears that the seemingly obvious starting point of top-down design — the view according to which every new development requires calling for some special function — is highly questionable:
A real system has no "top"!
The main function is often not only not the best criterion for the initial definition of a system, but it may also, in the course of the system's evolution, almost immediately turn out to be among the properties that change.
Consider, as an example, a program that has two versions: a "batch" one, which performs one large continuous computation during a session, and an interactive one, which in each session carries out a sequence of transactions, breaking the user's interaction with the system into smaller steps. Large scientific programs very often have two versions: one that "runs all night, performing a large chunk of computation", and another that "lets me first check a few things, look at the results, and then compute something else".
Top-down refinement of the batch version might begin as follows.
[B0] - Top-level abstraction "Solve a complete instance of the problem" [B1] - First refinement "Read the input data" "Compute the results" "Output the results"
and so on. Top-down design of the interactive version might proceed in the following style.
[I1]
"Process one transaction"
[I2]
if "The user has supplied new information" then
"Enter the information"
"Store it"
elseif "Previously given information has been requested" then
"Retrieve the requested information"
"Output it"
elseif "A result has been requested" then
if "The necessary information is available" then
"Obtain the requested result"
"Output it"
else
"Ask for confirmation of the request"
if Yes then
"Obtain the required information"
"Compute the requested result"
"Output the result"
end
end
end
(and so on)
Development begun in this way will lead to a completely incorrect result. The top-down approach is unable to account for the fact that the resulting programs must be nothing other than two versions of one and the same software system, regardless of whether they are designed simultaneously or one is derived from the other.
This example highlights two of the most unpleasant consequences of the top-down approach: first, it focuses on the external interface (here this showed up as an early choice between the batch and interactive versions); second, it prematurely establishes temporal relations (that is, the order in which actions are executed).
The architecture of a system should be based on content, not on form. But top-down design tends to use, as the basis for the structure, the most superficial aspect of the system — its external interface.
Such an emphasis on the external interface is unavoidable for a method whose key question is: "What will the system do for the end user?" The answer to it will inevitably be focused on the most external aspects.
The user interface, as a rule, turns out to be one of the most changeable components, since it is difficult to get the interface right on the first attempt. Quite often it is possible to build the interface separately from the other components of the system, using one of the many tools available today for implementing elegant and friendly interfaces based on OO methods. In such cases, the user interface has almost no influence on the design of the system as a whole.
The preceding examples also illustrate another shortcoming of top-down functional decomposition: the premature fixing of temporal constraints. Each refinement unfolds part of the abstract structure into a more detailed control architecture, specifying the order of execution of the various functions (the various parts of the corresponding action). Such refinements and ordering constraints become essential properties of the system's architecture, but they are also subject to change.
Let us recall the two alternative structures for the first refinement of the compiler. [C1] "Read the program and produce a sequence of tokens" "Parse the sequence of tokens and build an abstract syntax tree" "Annotate the tree with semantic information" "Generate code from the resulting tree" [C'1] from "Initialize the data structures" until "All function definitions have been processed" loop "Read the definition of the next function" "Generate partial code" end
"Fill in the cross-references"
As in the previous example, we begin with two completely different architectures. Each of them is defined by some control structure (a sequence of instructions in the first case, and a loop followed by an instruction in the second), imposing strict constraints on the order of the elements within that structure. But it would be unwise to fix such ordering relations at the earliest stages of design. Questions such as the number of compiler passes, and the sequencing of the various phases (lexical analysis, parsing, semantic processing, optimization), have many different solutions, which the developers must arrive at by taking into account the trade-offs between memory and time, and other criteria that they may not have been guided by at the start of the project. They may successfully carry out the work of designing and implementing individual components long before fixing the temporal order between them, and will want to keep their freedom to choose that order for as long as possible. Top-down functional design does not provide such flexibility: it requires the order of execution of operations to be determined before a deep understanding of what those operations will do has emerged.
OO design avoids premature ordering. The developer studies the various operations applicable to certain data and specifies the result of each one, while postponing, as far as possible, the determination of the order in which the operations are executed. This can be called the shopping-list approach: here its role is played by the list of necessary operations, that is, all the operations that you might need. No constraints are imposed on their order during software construction for as long as possible. The result is architectures that are far more flexible.
The risk of premature ordering deserves closer examination, since even OO designers are not immune to it. The shopping-list approach is one of the least well understood components of the method. Quite often one can find OO designs that have fallen into the old trap, which immediately shows in their quality. In particular, this can result from misuse of the idea of case analysis — case technology, which we will encounter when studying OO methodology.
The problem is that the order of operations, which seems like an obvious property of the system and appears to commit nothing at the early stages of design, has dreadful consequences if, after all the refinements, it has to be changed. The alternative method — the shopping-list approach — may seem less natural at first glance, but is significantly more flexible, since it uses logical rather than temporal constraints. It is based on the concept of assertions, developed later in this book (see ). Let us now demonstrate the basic ideas with a non-programming example.
Consider the problem of buying a house, reducing it to three operations: finding a suitable house, obtaining a loan, signing the contract. Using the ordering-based method, we would describe our design as a simple sequence of steps:
[H]
find_house
get_loan
sign_contract
In the shopping-list approach to OO development, at this stage we would refuse to attach so much importance to the order of operations. But, of course, constraints between the operations do exist — you cannot sign the contract if you do not have a suitable house and do not have the money to buy it. We can express these constraints in logical form:
[H1]
find_house
ensure
house_found
get_loan
ensure
loan_obtained
sign_contract
require
house_found and loan_obtained
The notation will only be introduced in , but even here everything should be clear enough. The require clause specifies a precondition, a logical property required by the operation before it is executed; ensure specifies a postcondition, a property established once the operation has completed. In this way we have managed to describe the result of the two operations, and the fact that the last operation requires, in order to be executed, that the results of these operations have been achieved.
Why is the logical form H1, which establishes constraints, better than the temporal form H? The answer is clear: H1 expresses the minimum requirements, avoiding the over-specification characteristic of H. Indeed, why not obtain the loan earlier and only then think about buying a house, having a certain amount of money available — this tactic may well be justified for a buyer whose main concern is financial. As far as possible, both possible orders of action should be supported, while respecting the logical constraints.
An approach based not on the order of operations but on logical constraints is more balanced. Each operation simply states what it needs and what it guarantees — all this in terms of abstract properties.
These remarks matter, in particular, for object-oriented designers as well, who may still be captive to functional ideas and will try to apply early system identification using scenarios (case technology) as the basis for analysis. This is incompatible with OO principles and often results, in its pure form, in top-down functional decomposition, even if the team members are convinced that they are using an OO method.
After this brief foray into object territory, let us return to the analysis of the top-down method and examine it this time with respect to one of our main goals — the reusability of software.
In top-down development, program elements are created in response to individual refined specifications encountered in the tree-shaped design of the system. At the current point in development, corresponding to the refinement of some node of the tree, the developer will become aware of the need to introduce some function, for example parsing an input command line. Its specification will then be set, and the function may well be implemented by a different person.

Fig. 5.4. A module's context in top-down development
The figure, showing part of the top-down refinement tree, illustrates this property: C2 is written to satisfy some part of the requirements of C. The characteristics of C2 are entirely determined by its immediate context, that is, by the needs of C. For example, C might be a module responsible for parsing some input data, and C2 might be a module responsible for parsing a single line (a part of the whole long input).
Such an approach ensures a good match between the design and its initial specification, but does not promote its reuse. Modules are developed in response to individual subtasks as they arise and, as a rule, are no more general than their immediate context forces them to be. In our example, if C is intended for a special kind of input text, then it is unlikely that C2, which parses a single line of such text, will be applicable to any other kind of input.
Design aimed at reusability implies building the most general components possible, out of which systems are then assembled. This process goes bottom-up and is the opposite of the idea of top-down design, which requires starting from the definition of the "task" and deriving its solution through successive refinements.
This discussion shows that top-down design is a byproduct of what might be called the cult of the project in software development, which holds that the individual project, unconnected in any way to previous or subsequent projects, should be the unit of consideration. Reality is not so simple: a company's n-th project is usually a variation on its (n-1)-th project and a precursor to its (n+1)-th. By focusing on a single project alone, top-down development neglects this feature of practical software construction.
One of the reasons for the initial appeal of top-down design ideas is that this style can be convenient for explaining each step of the development. But what is good for documenting an existing development is not necessarily the best way of carrying it out. This point of view was vividly expressed by Michael Jackson in "System Development" ([Jackson 1983], pp. 370-371):
"Top-down is a sensible way of describing things that are already fully understood. But it is not a suitable way of designing, developing, or discovering something new. There is a close parallel here with mathematics. In mathematics textbooks, the individual disciplines are described in logical order: each theorem, once stated and proved, is used in proving the theorems that follow. But in reality these theorems were not created or discovered in the ways or in the order indicated... If the designer of a system or a program already has a clear picture in his head of the final result, then he can apply the top-down method to describe on paper what he already has in his head. That is precisely why people may believe that they are designing and developing top-down, and doing so quite successfully: they confuse the way of describing something with the method of developing it. By the time the top-down stage begins, the problem has already been solved, and all that remains is to work out a few details."
The discussion of functional top-down design shows that this method is poorly suited to the development of significant systems. It remains a useful paradigm for small programs and individual algorithms, and it is also useful for describing well-understood algorithms, especially in programming textbooks. But it does not scale and is not suited to large practical software systems.
Using objects (or, more precisely, as we will see later, object types) as the key to splitting a system into modules is based on the substantive goals defined in , in particular on extendibility, reusability, and compatibility.
The arguments in favor of using objects will be fairly brief, since this question has already been examined earlier: many of the arguments against function-based top-down design naturally turn into evidence in favor of object-based bottom-up design.
This evidence, however, should not lead to a complete rejection of functions. As noted at the start of the lecture, no approach to software construction can be complete unless it takes both sides into account — functions and objects. We therefore need, even in the OO method, to preserve a proper place for functions, even if in the resulting system architecture they are subordinate to objects. The notion of an abstract data type will provide us with a definition of objects in which a suitable place is reserved for functions.
Since the functions of a system tend to change over its lifetime, the question arises of finding a more stable characterization of its essential properties, one that could guide our choice of modules and would match the goal of continuity.
The types of objects that a system works with are more promising candidates. Whatever happens to the payroll system used in the example above, it will still manipulate objects representing employees, salary schedules, company policies, timesheets, paychecks. Whatever happens to a compiler or other language-processing tool, it will still manipulate source texts, sequences of tokens, parse trees, abstract syntax trees, target code. Whatever happens to a system implementing the finite element method, it will still manipulate matrices, finite elements, and meshes.
This argument is valid only when objects are considered at a sufficiently high level. If objects are considered at the level of their physical representations, extendibility will be little better than that of functions — in fact, even worse, since top-down functional decomposition at least supports abstraction. The question of finding the appropriate level of abstraction for describing objects is therefore key, and the rest of this lecture will be devoted to it.
The discussion of reusability showed that a procedure (an element of functional decomposition) is usually not sufficient as a unit of reuse.
We considered earlier () a typical example: table search. Starting from what seemed a natural candidate for reuse — the search procedure — we realized that it could not be reused separately from the other operations applied to the table, such as insertion and deletion.
This is where the idea comes from that, in this problem, the module that reasonably admits reuse should be the collection of such operations. But if we try to understand what concept unites all these operations, we find the type of objects to which they are applied — tables.
Such examples suggest that object types, fully equipped with their associated operations, are indeed the stable units for reuse.
Another indicator of software quality, compatibility, was defined as the ease with which software products (in this discussion, modules) can be combined with one another.
If data structures were not designed with this goal in mind, then the actions that access them are very difficult to combine. So why not try combining whole data structures instead?
We have now accumulated enough grounds to attempt a definition of OO software construction. This will be only a first sketch; a more concrete definition will follow in the next lecture.
OO software construction (definition 1)
OO software construction is a method of software development that bases the architecture of every software system on modules derived from the types of objects it works with (rather than on one or more functions that it is supposed to provide).
A concise characterization of this approach could serve as the OO designer's motto:
The object motto
Do not ask first what the system does.
Ask who in the system does it!
To obtain a working implementation, you will sooner or later have to find out what it does. Hence the word first. OO wisdom says it is better to find out what is being done later rather than sooner. In this approach, choosing the main function is one of the last steps in the process of constructing the system.
Instead of searching for the system's topmost function, the types of objects that make it up will be analyzed. The design of the system will move forward through a successive improvement in our understanding of the classes of these objects. This is a bottom-up process of building stable and extendible solutions for individual parts of the problem and assembling from them ever more powerful building blocks, until the final block is obtained, delivering the solution to the original problem. In doing so, one can hope that it is not the only possible one: if the method is applied correctly, the same components, assembled differently and possibly combined with others, will prove general enough to also yield, as a byproduct, solutions to some new problems.
For many software developers, such a change of viewpoint is just as shocking as the idea that the earth revolves around the sun, rather than the other way around, might once have been shocking to many. It also runs counter to much of established software development practice, which tends to present system construction as the execution of a system function, laid out in a detailed, requirements-bound document. Nevertheless, this simple idea — considering the data first, setting aside the system's immediate purpose — can serve as the key to reuse and extendibility.
The definition given above will serve as the starting point for discussing the OO method. It not only provides answers to some questions relevant to OO design, but also prompts many new questions, such as:
[x]. How does one find the relevant object types?
[x]. How does one describe object types?
[x]. How does one describe the relationships between object types and their proximity?
[x]. How does one use object types to structure software?
The rest of this book will be devoted to answering these questions. Let us first look at some preliminary answers.
The question "how are we going to find objects?" may at first seem daunting. In the course "Fundamentals of Object-Oriented Design" we will examine it in more detail, but here it is useful to dispel some of the fears that arise. This question need not take up much time for experienced OO developers, in particular thanks to the availability of three sources of answers:
[x]. Many objects lie right on the surface. They directly model objects of the physical reality to which the software is applied. OO technology is a powerful modeling tool, using types of software objects (classes) to model types of physical objects, and relations between object types (client, inheritance) to model relations between types of physical objects, such as aggregation and specialization. A software developer does not need to study treatises on OO analysis to use a class CALL and a class LINE in a telecommunications monitoring system, or a class DOCUMENT, a class PARAGRAPH, and a class FONT in a document processing system.
[x]. One source of object types is reuse: classes previously defined by others. This method, not always prominent in the literature on OO analysis, often turns out in practice to be the most useful one. We must resist the temptation to invent something if the problem has already been satisfactorily solved by others.
[x]. Finally, experience and imitation also play an important role. Having become familiar with successful OO developments and sample designs, a designer can draw inspiration from these earlier efforts.
We will better understand these and other methods of identifying objects once we acquire a deeper understanding of the essence of the notion "object" in programming — which should not be confused with the everyday meaning of the word.
Suppose it is known how to obtain the appropriate object types that serve as the basis for the structure of our system's modules. Then the question immediately arises of how to describe these types and their objects.
In answering it, we should be guided by two requirements:
[x]. We must strive for independence of the descriptions from representations, so as not to lose the main advantage of top-down design: abstraction.
[x]. We must find a suitable place for functions in the architecture of programs whose decomposition is based on the analysis of object types, since both of the dual aspects — objects and functions — must be given their proper place in it.
The next lecture develops a technique for describing objects that makes it possible to achieve both of these goals.
Another question concerns which relations are permitted between object types. In refined object technology there are only two relations: "being a client" and inheritance. They correspond to different kinds of possible dependencies between two object types A and B:
B is a client of A if every object of type B contains information about one or more objects of type A.
B is a descendant of A if B represents a specialized version of A.
In some approaches to analysis, in particular in an approach to information modeling such as entity-relationship modeling, a richer set of relations is used to describe the possible connections between the elements of a system. For people accustomed to such approaches, it may at first seem quite inconvenient to work with only two kinds of relations. But this fear may prove unfounded:
[x]. The "being a client" relation is quite broad and covers many kinds of dependencies. Examples of such dependencies include the relation often called aggregation (the presence, in every object of type B, of a subobject of type A), as well as reference dependency and generic dependency.
[x]. The inheritance relation covers numerous forms of specialization.
[x]. Many dependencies can be expressed in general form by other means. For example, to describe a "1 to n" dependency (every object of type B is connected to no fewer than one and no more than n objects of type A), we state that B is a client of A, and attach a class invariant that precisely defines the nature of the "being a client" relation. Since class invariants are expressed using a logical language, they cover far more different relations than the entity-relationship approach or other similar approaches can offer.
[x]. Computation involves three kinds of ingredients: processors (or threads of control), actions (or functions), and data (or objects).
[x]. The architecture of a system can be obtained either from functions or from object types.
[x]. A description based on object types provides, over time, better stability and better possibilities for reuse than a description based on the analysis of the system's functions.
[x]. As a rule, it is unnatural to consider that a system's task consists in implementing only a single function. A real system usually does not have a single "top", and is better described as a system providing a range of services.
[x]. At the early stages of system design and development, one should not devote much attention to constraints on the order of actions. Many temporal relations can be described more abstractly in the form of logical constraints.
[x]. Top-down functional design is not suited to software systems with a long life cycle that includes their modification and reuse.
[x]. In OO software construction, the structure of the system is based on the types of objects it works with.
[x]. In OO development, the initial question is not what the system does, but with what types of objects it does it. The decision as to which function is the system's topmost function (and whether there even is one) is postponed to the final stages of the design process.
[x]. For the software being designed to be extendible and to allow reuse, OO construction must derive the architecture from sufficiently abstract descriptions of objects.
[x]. Two kinds of relations can exist between object types: "being a client" and inheritance.
The question of OO decomposition is discussed with various arguments in [Cox 1990] (originally in 1986), [Goldberg 1981], [Goldberg 1985], [Page-Jones 1995] and [M 1978], [M 1979], [M 1983], [M 1987], [M 1988].
The top-down design method is advocated in many books and articles. Wirth [Wirth 1971] developed the notion of stepwise refinement.
Among other methods, the closest one would appear to be Jackson's structured design method, JSD [Jackson 1983], and its high-level extension in [Jackson 1975]. See also Warnier's data-driven design method [Orr 1977]. For an introduction to the methods that OO technology is meant to replace, see the books on the Constantine and Yourdon structured design method [Yourdon 1979], on structured analysis [DeMarco 1978], [Page-Jones 1980], [McMenamin 1984], [Yourdon 1989]; and on the Merise method [Tardieu 1984], [Tabourier 1986].
The entity-relationship modeling method was introduced by Chen [Chen 1976].
Comments