Going "object-oriented" does help; but it does not eliminate all difficulties. Further, going object-oriented does introduce new problems - the problems of finding the objects and identifying their classes. It is easy to find the objects and their classes in problems like Space Invaders or Cards. A program such as FSM is a bit more realistic and more work was needed to identify its classes; but the analysis for FSM was greatly simplified from the start by the knowledge that the program was intended to be a standard palette-based editor built on top of a framework library. With more complex applications, it can be quite difficult to identify the "correct" classes. In extreme cases, one analyst's object can be another analyst's method (just as in US-English dialect, where one can "nounify" every verb and "verbify" every noun).
There are frequent calls for an OO methodology - some definitive prescription of a reliable way of developing OO programs. The dictionary definition of methodology is "a body of methods used in a particular branch of activity" and a method is "a special form of procedure". If there was a methodology we'd all be out of jobs. If there really was a set of procedures that could be executed to create a new program, someone would code them up, add a Windows-3.1 interface and sell the ultimate CASE tool. There is no methodology.
Stroustrup: Design and programming are human activities; forget that and all is lost.
Brooks: Software construction is a creative process. Sound methodology can empower and liberate the creative mind; it cannot inflame or inspire the drudge.
Booch: The amateur software engineer is always in search of some sort of magic, some earthshaking technological innovation whose application will immediately make the process of software development easy. ... Professionals know that rigid approaches to design lead only to largely useless design products that resemble a progression of lies, behind which developers shield themselves because no one is willing to admit that poor design decisions should be changed early, rather than late.
However, there is no list given of activities that one can work through mechanically (and have ticked off on some managerial chart) and be guaranteed of arrival at a working OO system. Moreover, real projects involve work by development teams and such team projects entail a whole new series of problems.
There are numerous (somewhat conflicting) proposals as to how to carry out OO analysis and design of more complex projects. The text books listed at the end of this chapter present a few of the proposed approaches. Some of the texts present relatively simple approaches that may be relevant even to small individual projects (e.g. the book by Wirfs-Brock et al., or the text by Henderson-Sellers). The other texts should be left until beginners have gained some familiarity with an OO language and OO approaches on small projects, and are starting to work with a group on some larger development.
The following sections focus on the usual phases of a project: Analysis, Design, Detailed Design, and Implementation. However, it must be remembered that these phases are not as clearly distinct in an OO development as they were in conventional projects. The individual phases tend to merge and the entire process often involves some degree of iteration. For example, the initial analysis process will often identify clusters of "proto classes" (things that look like candidates for being classes, but may not correspond to classes in the developed system). These clusters are formed around proto classes that apparently should have strong mutual interactions, but only limited interactions with the rest of the components of the proposed system. One such cluster might have to be explored first; the analysis process being extended into design, and possibly even prototype implementation, in order to refine ideas as to the kinds of service that the cluster might reasonably offer. Once these services are better defined, the development group can return to the analysis of the overall problem. Subsequently, the initial design of the classes in the cluster may be revised; certainly any prototype implementation will be reworked. If changes at these levels alter the services provided by the cluster, then the developers would have to reiterate through the overall systems analysis stage.
Some limited amount of prototyping of code is likely to be necessary in OO developments. Smalltalk environments encourage an approach where one analyses a bit, designs a bit, implements a bit and starts over; eventually, the entire proposed system will get to be "prototyped" (sometimes with the unfortunate result of the prototype implementation being put into production use). Prototyping of a complete system is not quite so practical with C++ developments, but typically some parts of a project are prototyped in order to refine the analysts' understanding of the problem. This is not in the Smalltalk style of building a complete system; the prototyping is done to resolve specific problems that arise during the analysis. Preliminary implementation work may be done on a small part of the system to resolve whether some proposed algorithm is practical, or to allow potential users to evaluate a proposed mechanism for interacting with the program. Prototyping of an algorithm should be done in C++, but prototyping of interfaces is usually better done using specialized tools that allow "working" mock-ups of interfaces to be constructed quickly.
One does not typically start by trying to identify "classes". The classes come later, and class hierarchies come later still. The place to start is the "objects". (The "objects" identified during this first preliminary analysis may not be quite the right ones, they may turn out not even to be objects. They'll be referred to below as "candidate objects" or "proto-objects".)
The objects of interest are going to be those that exist in the computer's memory when the program is executing. Each object has (should have!) a specific, well defined role - it will own some resources and provide services based on the resources that it owns. A program's objects are supposed to be analogous to real world physical objects that obviously do exist, do own resources and do perform tasks. Identifying real-world objects is sometimes a useful point to start a search for the objects needed in a computer program; but only a starting point.
While an OO program is often a "simulation of the real world", it is rarely an exact simulation. The objects that exist in an OO program may well be fictions. For example, an OO payroll program is more likely to have intelligent "paycheck" objects (that can work out their amount and then print themselves) rather than a collection of objects that really do model the actual time-sheets, finance clerks, and accounting officers that are involved in the real world situation. Such intelligent paychecks are a fiction; but they constitute a more useful basis for an effective computer implementation than would classes that more accurately model the real world.
The analyst's first task is to identify the useful objects such as intelligent paychecks, along with display view objects and all the rest. Given the current fashion for "user-friendly" WIMPy (Window, Icon, Menu, Pointer) programs, it is inevitable that similar subsystems, groups of objects, are found in most applications (e.g. a subsystem with View objects etc. for the user interface; a "document" object to organize the storage of other objects in working memory and the transfer of persistent objects to disk storage). Such standard components can be dealt with fairly quickly and superficially during the preliminary analysis which should focus primarily on the new, application-specific components.
Of course, nothing is that simple. The typical application specification will refer to things that are outside the scope of the actual implemented program (e.g. "the user"); the initial list of nouns must be filtered to exclude these external components. Usually, several different nouns, or noun-phrases, are used to describe the same thing (e.g. the user, the customer, he/she); the list of nouns must again be filtered to eliminate such alternatives and retain only a single standard term (a noun or noun phrase) for each "thing" involved.
Some of the remaining nouns will indeed identify objects that will be present in the program; others don't merit the role of being separate objects (except in Smalltalk where everything is inherently an object), they will exist simply as attributes (data members) of "real" objects. To be worth considering as a real object in this analysis phase, the "thing" has got to own a reasonable number of resources and provide a range of services relating to those resources. So in a banking application, an Account is likely to be a reasonable analysis object. In some way or other an Account "owns" details of an account-holder, a balance, and a transaction record, and it can accept a deposit, process a debit, print a warning letter when it goes into deficit etc. A Balance is probably not worth considering as a separate object during analysis. A Balance can only store a value and, at most, respond to Value(), +(increment), and -(decrement) "messages". A Balance should be treated just as a data attribute of an Account, probably being represented as a fixed-point number. As far as the analysis is concerned, details of the "account-holder" could also be treated as a simple struct data attribute - even though the implementation might well utilize a class AccountHolder rather than a simple struct for this data attribute.
After filtering the noun list to remove external entities, alternative names, and "data attributes", the analyst will be left with a list of candidate objects that would need to be evaluated further. The responsibilities of different candidate objects must then be determined using action verbs as an initial guide to the range of possible responsibilities.
Thus, an analyst working on the chemistry editor problem, Figure 11.1, should start by recruiting Sue (the chemist client) into the analysis team.
The analysis group should then proceed by developing numerous "scenarios" that each represent some part of the work carried out by the planned program. One such scenario is illustrated in Figure 11.2. This scenario is one of those created when considering how the actual editing of the chemical structure might be performed. The editing process would almost certainly involve some form of palette-based editor, with a palette of "tools" that the chemist can use to add substructural components (possibly overlapping with existing parts of a structure), change atoms, alter bond orders etc. The client and the analyst have to resolve how the editing is done, determine the range of "tools" required, and identify additional requirements (such as the possibility that a subsequently added component may have to overlap with and merge into existing structural features).
As shown in Figure 11.2, each scenario involves a number of perspectives (just two in Figure 11.2). Working with the client, the analyst develops the "user perspective". This details the input actions of the user and suggests the form of the program's responses. In the various panes shown in Figure 11.2, the user is shown selecting a tool from the palette and using this to make a substructure (a predefined standard assembly of atoms and bonds) the basis of the new structure.
Along with each pane in the client's perspective, the analyst sketches out a first guess as to the "objects" that might be involved in carrying out the tasks proposed. If the program is to have a tools-palette, then there must be some kind of Palette object present at run-time that will look after these "tools".
Similarly, handling the user's drawing action is going to have to involve some objects. The analyst would probably be able to identify the need for a ChemView object (with responsibilities that include arranging the display of the structure and responding to mouse-clicks). The ChemView object must be in existence prior to the start of this scenario (note to self: must determine when the ChemView was created and what created it). Its response to the mouse click will be the creation of some form of PartSketcher object that will actually draw the additional structural features and allow them to be positioned.
The fourth pane in Figure 11.2 shows, in the user perspective, the start of a subsequent editing operation. In the system perspective, the analyst must diagram the interactions among objects that are necessary to complete the first editing step. If it had not already done so, the PartSketcher would have to create a Part object that represents the appropriate substructure with its atoms and bonds, and then arrange for the Part object to be handed over to a ChemStructure object with the request that it be merged with any existing structural components.
Every processing step in the program will be initiated by some user action. So, by exploring scenarios for all the different options required by the user, the analyst can identify candidates for all the main objects that will be needed in the system. Figure 11.3 contains a fragment from another scenario, which explored the program's response to a user request to transmit the structure as a query to a database server.
The analyst may work with several domain specialists while developing such scenarios. For the scenario relating to Figure 11.3, the analyst had to work with a programmer familiar with interprocess communication on the Internet. Discussions with the programmer helped identify the "objects" that would be required during an Internet session.
The scenarios that are developed by the analyst and domain experts have value quite apart from their role of assisting the analyst in the identification of proto-objects. The information in the "User perspective" becomes the basis for the user manuals and tutorials that will eventually have to be created for the program. The "system perspective" information has a similar use as a "tutorial" for the eventual maintenance programmers; these diagrams represent the program's principal objects and interactions. Of course, both parts of these scenario diagrams are going to have to be revised as the initial analysis is refined and again later when design glitches necessitate some reworking of these initial ideas.
By exploring all the ways that the user can interact with the program, the analyst obtains a list of proto-objects and ideas of their responsibilities. Some, but not all of these proto-objects will correspond to objects in the implemented program. The next step in the analysis is to begin to identify classes.
These initial ideas for proto-classes have to be documented. For very simple cases, one can use the "fuzzy analysis" diagrams that have been employed in earlier chapters (e.g. Figure 8.12). Often, some form of "Class-Responsibility-Collaboration" card record, e.g. Figure 11.4, is useful.
"Class-Responsibility-Collaboration" (CRC) cards (originally proposed by Beck and Cunningham) are recommended by Wirfs-Brock et al. The cards are typically real physical (5"x3") record cards or something similar. Each card identifies a proto-class, and provides for later insertion of cross-references to super- and sub-classes once these have been identified. In the main body of a card, one lists the responsibilities of an instance of the class, and notes those other classes with which there will be collaborations. These data are obtained by examining the various scenarios that have been developed earlier. (Alternatively, in the more linguistically-oriented approaches, such cards are created for each of the nouns selected from the specification document, with the responsibilities being derived from the verbs.)
The use of real physical cards has been justified by empirical results from observations of developers trying to create OO programs. The actual physical cards help make a problem more concrete. Developers can move cards around, join cards with threads that represent interactions ("my document will need to tell your view to do an update, so let's join them with this blue thread to represent that interaction"). Use of the physical cards enables designers to play out scenarios and explore different organizations. Even if such playacting doesn't improve the analysis and design, it may well help to develop cohesion in a project group that is just commencing a major development.
Often, more detail is required than would be included in a simple CRC card. An alternative version is illustrated in Figure 11.5. The form shown is intended primarily for a hypertext environment where there are active links among different records. The class is identified, with a Booch-style fuzzy class outline (identifying this as an analysis level diagram). The responsibilities of the class are separated into "resources owned" and "services provided". Similarly, collaborations are made more explicit, with clients and servers separately identified. The class of a client that would use each service is indicated along with that service. Instances of the displayed class, e.g. ChemStructure, will collaborate with instances of other "server" classes to get particular work done; thus, a ChemStructure object will require the services of some collaborating Collection object to store its atoms and bonds. There should be cross references to each of the scenarios where instances of the class appear.
If a hypertext system is used, the fields in such records can all be active. Selection of a data resource will automatically link to a more detailed description of that data member; this record would suggest the data type, initial values etc. The references to client and server collaborating classes would link to the appropriate class descriptions. If the scenario data are also held in hypertext form, these can be reached via the scenario references. Other links would connect an analysis level diagram to the corresponding record(s) that detail the classes as revised during the design phase. Another link could connect to any "transition state diagram" (see below) that might be used to characterize a class.
The classes identified during these early analysis stages are not necessarily going to appear in the later design. The analyst has to describe what the program is to do, not how its work is accomplished. This allows the analyst to use more abstract, higher level descriptions. For example, in the scenario in Figure 11.2, the analyst identified a Palette object (instance of class Palette) that represented the tools palette. Class Palette is a useful fiction for understanding the overall behaviour of the program.
However, if a single class Palette were carried through into the design and implementation, it would almost certainly prove to be overly complex with too many and too varied responsibilities. A more likely implementation would be as in the FSM program, where the work ascribed here to Palette was divided among class PaletteView, class PaletteManager, and the various PaletteItem classes. Class Palette is not in any way an abstraction of these actual design/implementation classes; it exists only at the analysis level, and its responsibilities are completely remapped in the design into other quite distinct classes that come from quite separate parts of the final class hierarchy.
A multifaceted analysis class, like class Palette, will often be replaced in the design by separate classes each of which captures only one aspect of the original behaviour. Another quite common situation is for the analysis to involve a "class" that seems to represent a useful abstraction but again in the design is replaced by a set of classes that are not hierarchically related.
For example, it may well be convenient for the analyst to work in terms of a Tools class - the chemistry editor might need one set of tools for adding substructures, changing bond orders etc. and another set for selecting data from the tables retrieved from the database server (e.g. a tool that selects a series of numerical values and draws them as a graph). As far as the analyst is concerned, such "tools" are very similar and belong to some overall Tools class. At the design level, it might well emerge that there was essentially nothing in common to among these different kinds of tools. They might all be related to a framework provided class Command (because most would be associated with reversible actions) but apart from this they might have no common data members and no common behaviours. The analysts class Tools would again disappear at the design level being replaced by a number of classes.
The analyst's concept of Tools is more like a category rather than a class:
categories : a priori conceptions applied by the mind to sense impressions, or relatively fundamental philosophical concepts. class : group of persons or things having some characteristic in common.The concept of a "tool" is useful as a means to get to understand the overall system. But there should be no mandatory requirement that the later design and implementation actually include any abstract class Tool. If there are no common characteristics, the individual "tools" should be instances of distinct, essentially unrelated classes.
"Classes" that turn out to be categories are introduced quite often during the early stages in analysis. This is only natural. The analysts are trying to impose some order on the chaos of different things that emerge from the scenarios that are being created to characterize a proposed application. Consequently, they impose preconceived ideas ("this application is a specialized editor, it is bound to have a lot of editing 'tools', let's identify some"). The development team must simply be aware that some of the analysis level classes may turn out to be incoherent when, during the design process, an attempt is made to find common behaviours that can be associated with an abstract class. Then, another iteration can be made through the analysis process, or the developers can simply note the mapping from the analysis category to the various designed classes.
The analysts are not responsible for finding all the classes that exist in the implemented system. The analysts are concerned only with what the program must do. Many other classes are introduced as part of the design process when decisions are made as to how the program's tasks are to be accomplished. A few classes may be introduced during implementation.
For example, during the analysis phase for the chemistry editor, there is no need to consider how the atoms and bonds are to be represented. Obviously, they can be represented - maybe as integers (held in an array), or as a list of structs, or as instances of specialized classes. The decisions regarding the representation of atoms etc. can be left to the designers who may choose to introduce a class Atom. Similarly, the designers could specify the storage structure to hold the atoms belonging to a ChemStructure, but, again, this decision can be deferred to the implementors. It might be sensible to implement some simple storage scheme (using an instance of a standard TList class or ObjList class from a framework class library) and then obtain some performance statistics through trial use in order to determine a more optimal storage structure. For example, if it emerged that the majority of the chemical structures created were natural products having a total of between 50 and 70 atoms, it would probably be sensible to change the storage structure to some form of dynamic array with an initial size of 60 elements. If the structures varied widely in size, the default list based implementation might be more satisfactory and so could be retained.
Sometimes, the problem domain provides a natural classification hierarchy that can be used to interrelate different objects. Thus, a banking application might require "Account" objects with specializations such as "Checking Account", "Savings Account", "Mortgage Loan Account", "Personal Loan Account", etc. The analyst can obviously identify such class hierarchies and specify their existence as constraints on the design. With the exception of those hierarchic structures that are inherent in the problem domain, the analyst should not be overly concerned with hierarchic relations among classes.
In the discussion of Figure 11.5, it was noted that the analysts might need to include a state diagram in the document of a class. This is not that common (such diagrams were not needed in any of the examples in Chapters 6, 8, or 10), but does sometimes occur. An object that is an instance of some class may exist in only a finite number of well defined states; with each state accepting only a subset of the "messages" that can be sent to such an object, and with most "messages" causing a transition to a different state. For example, one of the communications objects used in the chemistry editor program might have conceptual "states" such as 'initializing', 'getting password', 'logging on', ..., 'logging off' etc. A simple diagram showing the states and allowed transitions is often an effective way of characterizing such classes. Where a state model exists, it should be documented as part of the analysis process.
Just as a final point on the analysis, when working with the client on scenarios, such as that in Figure 11.2, the analyst should not be thinking in terms of specific classes in some class library. It should not be a matter of "I can let you scroll that (note to self: specify a TScroller) and you'll be able to enter text directly on the item (note to self: specify a floating TEditText)". However, the analyst should be somewhat biased so that the chosen user interaction elements are easily realizable. This biasing is simply a matter of the analyst being familiar with the systems that the development team has successfully implemented. If a particular framework makes it a real pain to have a floating TEditText, then probably no delivered application would have included this feature; instead some data entry bar would always have been used. Knowing this, the analyst would propose a design with a data entry bar; the client will probably be quite content not to have to select between this and alternative mechanisms. It is the same kind of biasing towards the realizable that you'll find in other fields. Thus an architect, who has worked successfully with a subcontractor who can provide dry-climate indoor plants, would propose "a few Mexican cacti, or maybe a couple of Australian gum-trees" as the decoration for the atrium of a new office building. More difficult to achieve alternatives - "a nice bit of tropical rainforest, or a patch of subarctic tundra" - would simply not be offered to the clients.
Later, the designers start to explore hierarchical relations among classes. Often, as in pane 2 in Figure 11.6, this will be a matter of growing the tree downwards as specialized variants of classes are recognized. For example, the designers might identify two specialized kinds of table in the chemistry editor application - one for physical and spectral data (class SpectrumTable) and another for literature references (class LitRefTable). The designers will be hoping to reveal scope for shared implementations of routines at the abstract level, and "programming by differences" to clarify what is unique about the specialized subclasses.
At a still later stage, the designers will identify opportunities for the reuse of standard components. These opportunities may involve extending class hierarchies, as when "abstract class Table" is derived from a library class DynamicArray (pane 3 in Figure 11.6), or may be a matter of composition (e.g. class LitRefTable using an instance of class TextItem).
The early phases of the design process tend to be where one gets most iteration as ideas for classes and inter-class collaborations are proposed, evaluated, and revised. The analysts will have identified what the program is to do, but usually there will be many ways in which each task can be accomplished. The designers have to explore different approaches. The approaches will involve differing patterns of interaction among objects and, probably, slightly changed responsibilities for their classes.
More detailed specifications have to be created for the classes. Again, one of the more effective approaches is to follow through the scenarios. The process is similar to that in the analysis phase, it is simply much more detailed.
The level of detail should be at least that used when diagramming interactions among Document, Views, and other objects in the Cards and FSM example programs (e.g. Figures 8.15, 8.16 etc.). The diagrams should make explicit the sequence of method calls involved in each interaction. The designers should make a first attempt at identifying the data that must be transferred during these calls. (It is not worthwhile defining the method signatures at this stage. There will normally be some changes to the objects and classes during this initial design phase. Details of method signatures can be deferred to the detailed design phase that commences once the class definitions have become more stable.)
The interactions diagrammed in the analysis scenarios will omit details - details such as interactions with auxiliary objects and the handling of exceptional cases. For example, an analysis level scenario for showing the setting of "atom-type" in the chemistry editor would be similar to Figure 11.2 and simply show first the selection of the "Atom Namer" tool in the palette and then an AtomNamer command object being created after a mouse click in the main ChemView. Of course, the actual interactions are more complex. The mouse-click in the main ChemView would have to be shown as leading to it executing its HandleMouse() method. This method would involve a request to an associated Palette object to identify the current tool. The current tool identifier would be shown as being used in a switch statement that selects the processing option. The processing for an Atom Namer tool would involve a check that the mouse was located over an atom (one can't apply an Atom Namer tool to a bond or to a bit of empty space); only if the check were satisfied would an AtomNamer command object get created. The design scenario would have to make these details explicit.
The more detailed characterization of the processing involved in each method frequently requires the invention of additional private member functions that perform "housekeeping" duties. For example, a ChemView::HandleMouse() method that attempted to deal with all possible command objects and the constraints on their creation would prove overly long and complex. The designers would typically split out a number of private member functions to deal individually with different types of tool selection and constraint.
Information gained from each scenario should be transferred to more extended CRC cards defining each of the classes involved. Usually, analysis of design scenarios will result in additional data members that are pointers to instances of collaborating classes (and there may be a need for public interface functions that allow such pointers to be reset). As noted in the previous paragraph, the list of private housekeeping member functions may be expanded significantly. Additional public interface functions may be identified as extra responsibilities are accorded to the classes.
While scenarios can help identify the objects present at run-time and their mutual interactions, each scenario provides only a small glimpse as to the way in which an object from a particular class behaves. The designers should sketch out "life histories" for typical objects from each class. Such documentation helps implementors and maintenance programmers understand the role of the class of which the objects are instances. Further, it provides a way of checking that links are established before the object tries to interact with collaborators. A stylized example of such a life history diagram is shown in Figure 11.7.
Of course, the designers must create the algorithms for any non-trivial member functions. At worst, this is no harder than it would be when designing normal procedural code; often, it is simpler. Much of the code in a normal procedural program consists of selection statements (switch()... or if()... else... statements). Each of these selections is a repeat of the original specification where variant data forms were defined. In a proper OO implementation, most of these selections should disappear - being replaced by calls to dynamically bound functions (as has been illustrated in many earlier examples). But, of course, if the algorithm to be implemented is some elaborate iterative or recursive process, there is really no change from the ordinary procedural implementation.
When a complex data processing task is required, the public member function that defines the service will typically be implemented in terms of a number of private member functions. For example, in the chemistry editor program, one of the member functions of class ChemStructure would have to be something like ConvertToStandardForm(). When a structure is sketched in using the editing tools, its atoms will be numbered according to the order in which they were added. The programs at the database servers can only handle structures whose atoms are numbered in a standard, or "canonical" manner. There are rules that define whether a structure's atoms are numbered canonically. The ConvertToStandardForm() function would implement a form of search through the different possible ways of numbering the atoms to find the "canonical numbering". This recursive search would probably involve ten to fifteen auxiliary routines, each of which would end up as being defined as a private member function of class ChemStructure.
As details of the properties of classes are filled in, designers will encounter situations where extra classes are needed. Some of these extensions are simple and don't really change anything from the analysis model. For example, the designers creating class ChemStructure would almost certainly introduce class Atom and probably a class List to hold the atoms. Class Atom would be introduced because instances of this class can group together conceptually related simple data items (integer coordinates, an enum specifying the atom type etc.); such a class is little more than a struct record with a few methods for displaying itself, changing its data members, and performing file i/o etc. Class List would obviously be some simple collection class that would be expected to come from some standard class library.
Often, extra classes will be created either to isolate knowledge, or to reduce mutual interactions among existing classes, or to separate an overly complex class into a number of classes each capturing just one facet of the original class's behaviours.
As a design develops, it is not uncommon to find a class that needs to know too much about other classes. For example, if class ChemView really must handle creation of command objects for visual editing, it will need to know about all the different kinds of editing command and the various constraints that apply to their use. Such dissemination of knowledge makes a program more difficult to maintain and extend. Any change to the palette of editing tools is going to have effects on class Palette, class ChemView and maybe others. When a class appears to have too much knowledge of other classes, designers should consider introducing an intermediary. This kind situation arose in the FSM program and was one of the reasons behind the introduction of class PaletteManager and class PaletteItem. Class PaletteManager localized knowledge concerning the number of different tools. The individual PaletteItems localized knowledge concerning the constraints that applied to the use of the different tools.
Sometimes, examination of the design scenarios will reveal cases where instances of different classes have too many interactions. An object of one class will be constantly interrogating another object as to the values of particular data members and requesting changes to those data members. Such interactions may be mutual; both objects may be "living in one another's pockets". One possible cause of such interactions is misassignment of responsibilities - ownership of the data resources in question may have to be reallocated. However, it is possible for such patterns of interaction to be evidence that a new class should be introduced. The new class would own those data members that seemed to need to be in common to the other classes, and would package all the services related to the management of these data resources. The original classes would be changed so that each had a pointer to an instance of the new class (and one would be made responsible for creating the instance at run-time and informing the other cooperating object of its existence).
As noted earlier, a class proposed by the analysts may turn out to have too many and too varied responsibilities. The designers may split such a class into several classes. Thus, class Palette may become class PaletteView (part of the View hierarchy in the final design) and class PaletteManager (from some totally unrelated branch of the final class hierarchy).
Addition of classes like class Atom and class List do not necessitate any change to the original analysis. Other additions to the set of classes planned for a program will constitute at least minor amendments to the original analysis model. Sometimes the changes can be described adequately in a simple concordance document that relates the design and analysis models (e.g. "Analysis class Palette was replaced by class PaletteView and class PaletteManager, PaletteView takes Palette's event-handling responsibilities with PaletteManager organizing data"). In other cases, the development group may need to make another quick iteration through the analysis phase to explore the impact of a proposed change.
Very occasionally, the designers will be able to eliminate one of the analysts' classes. Detailed consideration will show that two apparently distinct proto-classes are really the same class viewed from different perspectives. For example, in Figure 11.2 the analysis scenarios show a PartSketcher command object creating an instance of class Part (that was to represent the extra atoms and bonds that were to be added to a structure), and then getting the ChemStructure to merge in the Part. At the design phase, it emerged that a Part was really just a ChemStructure. Instances of these classes held similar data (although a few of the ChemStructure's data members would always NULL in a "Part") and had essentially the same behaviours. Class Part could be discarded as redundant.
Some of the analysts' classes will represent abstractions, with the actual objects created at run-time being instances of more specialized subclasses. Thus, the analysts for the chemistry editor might specify that instances of class Table get used to display the various different kinds of information retrieved from the remote databases. The designers would find that the information retrieved for literature references (a series of items each in the form authors, journal, volume, issue, pages, ...) was quite different from that returned for spectral data (a series of number pairs, e.g. m/z: [41,78], [43,83], [44,6], ...). Both kinds of information need to be displayed in a tabular form with a surrounding boxed outline, labelled columns etc. Given such substantial differences in the data, the designers would find it necessary to create specialized subclasses, class SpectrumTable and class LitRefTable. (Somewhat less commonly, the analyst will have specified the use of different kinds of table and left it to the designers to capture commonalities in what would normally be a fairly obvious common base class.)
Generally, the abstract class defined as a base for the various specializations will be "partially implemented". Some of the behaviours will be common to all (or most) of the specialized variants. Thus, there might be common code in class Table to draw an outline box with titles and to arrange column widths etc. This common Draw() method would include a call to a private virtual member function, DrawContent(), that would be implemented differently in the various specialized subclasses. As much as possible of the code should be defined in the base class to maximize reuse and avoid duplication of essentially equivalent functionality in the individual specialized classes.
If class Table has two specializations in the initial design, it is likely that in the final program it will have several more. (Additional specialized tables will inevitably be needed to accommodate all the other requirements that the client identifies on first playing with a prototype of the program.) The designers are going to have to give careful thought to the definition of their base class, providing for extension (through overridable virtual functions) and for controlled access to common data (through protected access methods). These provisions must be clearly documented so that other programmers coming later can understand how they may create further specialized variants.
It is at this stage of finding specialized versions of the analysts classes that the designers may discover that some of the original "classes" were merely "categories". An analyst might well describe editing actions on a ChemStructure and on Tables in terms of "tools" and have some class Tool that appears to serve as an abstract base class. The designers would find numerous specializations - AtomNamer tool, PartSketcher tool, ColumnSelector tool, etc. - as the individual scenarios were examined in detail. Apart from the fact that all these tools might appear to be related to command objects (as all will be involved in reversible editing actions), they may have nothing in common. Class Tool as an abstraction is eliminated. Either the individual specialized tools classes get to be derived directly from a Command class, or maybe separate intermediate abstract classes are identified (e.g. class StructureTool and class TableTool both derived from class Command).
Another important task of the designer is to discover new simplifying abstractions. For example, in the chemistry editor program, the analysts might have identified a Document object that owns an instance of class ChemStructure, two Tables (later differentiated by the designers into an instance of class SpectrumTable and an instance of class LitRefTable), an instance of class CASData (some other kind of retrieved information), etc. On the surface, there seems very little in common to these different classes. However, similarities are revealed if one looks at the pattern of communications between the Document object and these various other objects (Figure 11.8).
The similarities in these communications patterns suggests a possible role for an abstract super-class, class DataItem, that subsumes the various specialized classes that arose naturally during the analysis of the problem. Class DataItem could define a common interface - with consistent definitions for the signatures of the similar member functions DoRead(), DoWrite() etc.
Class DataItem would be a "pure abstract class". It would not implement any of the methods that it defines:
class DataItem {
public:
DataItem() {}
virtual ~DateItem() {}
virtual long What_Is_Your_Size() = 0;
virtual void DoRead(File*) = 0;
...
};
This type of abstract class does not bring any benefit from code sharing. There is no code at the abstract level to share. Each specialized class implements all of the defined methods along with any specific methods.
The benefit from such abstractions is that they help factor out the original specification. Instead of having a class Document that owns one each of a Chem-Structure, CASData etc:
class Document {
public:
...
private:
ChemStructure* fChemStruc;
CASData* fCas;
LitRefTable* fLits;
...
};
a revised design can have a Document that owns a list of DataItems:
class Document {
public:
...
private:
List fItemList;
...
};
Knowledge of the original specification for the program can thus be removed from class Document. Rather than explicitly saving, restoring and displaying the individual tables etc., its method can be written to iterate along the fItemList telling each item in turn to save, or restore, or draw itself. Such changes to the design make it practical to accommodate extensions requested later (such as some of Sue's subsequent requests, Figure 1.3, for multiple ChemStructures, additional data tables etc.).
The discovery of abstractions that allow for significant simplification of the design can enhance a system's reliability and potential for extension. These benefits outweigh the costs of the extra iterative cycles through the analysis and design that will be necessary to revise the structure that was initially proposed for the system
The use of instances of concrete library classes as components in new classes presents no problems. A bounding rectangle must be specified? Have a data member that is an instance of library class Rect. A header must be displayed above the data? Use an instance of library class TextItem (or its equivalent). Some items have to be stored in a list? Use an instance of library class ObjList etc. Quite a few of the decisions relating to the choice of concrete library classes can be deferred to the implementation phase.
The use of data members that are instances of concrete classes only comes up as a design issue if for some reason it becomes important to consider trade-offs between having an instance as an actual data member of a program specific class (expanded representation) or having a pointer data member with a separately allocated object in the heap. Small objects (such as Rects) should almost always be incorporated as (expanded) data members (the overhead of having them as separately allocated heap-based objects is excessive). If the data represent optional information that is potentially large (e.g. an instance of class TextStyle that is needed only if a non standard font is used), then separate allocation is almost always favourable. Apart from these two heuristics, little guidance can be given.
If a data member represents something that might be shared with another object, then the data should not be regarded as a component (an owned resource). The data member becomes a pointer. The instance of the concrete class becomes a collaborator and not a component.
The designers of programs that are to be built on top of framework class libraries will have been guided in their choice of classes by the concepts that are represented by the framework's principal classes. Consequently, the designers will have come up with a ChemDocument that owns data and talks to files, data will be displayed in ChemViews or PaletteViews or TableViews etc. There should be no difficulty in threading the program specific classes onto the framework classes. So, classes ChemView, PaletteView, and TableView become extensions of the framework's View hierarchy. Class ChemDocument is recognized as a specialization of Document etc.
When a program is being built using a framework class library, the designers must determine the extent to which specialized facilities such as object i/o and dependency mechanisms (see Chapter 9) will be used. Such decisions cannot be deferred to later detailed design and implementation stages because they have implications regarding the structure of the class hierarchies, the collaborators required by classes, and the patterns of interactions among instances of classes. For example, if the chemistry editor program were implemented in a simple fashion, any changes to an atom (e.g. change of type or position) will require that the ChemView be explicitly told to invalidate some subregion - so each atom would need a link to the collaborating ChemView and function Atom::ChangeType(Atype n) will need a call such as 'fMyChemView->Invalidate(aRect)'. If a dependency mechanism were being used, a change to an atom would be more likely to result in a "changed" message being sent to its ChemStructure and then propagated to the ChemStructure's dependents such as the ChemView (the message would specify that it was the atom that changed, so the view could ask the atom for its area and so do a minimal invalidate).
Inheritance can be overused. For example, Figure 11.6 suggests that class Table might be based on class DynamicArray. Such usage is dubious. It seems to be a case of "implementation inheritance". Class DynamicArray obviously provides some form of resizable storage and presumably has a public member function for adding another array entry; such facilities are likely to be quite useful in, for example, the communications code that builds a LitRefTable item by item as the literature references are returned by the database server. But in other ways, a LitRefTable doesn't behave much like a DynamicArray. It doesn't seem very likely that there would be much code like 'fLits-> At[index]' or code using any of the other member functions DynamicArray.
Rather than claim spurious inheritance from other classes, it is usually better design for a class to delegate work. Class Table should not be made a subclass of DynamicArray; instead, it should employ an instance of DyanamicArray.
A class design diagram, such as shown in Figure 11.9, provides a succinct overview of a class. Some notational schemes rely on subtle visual cues to distinguish between abstract and concrete classes etc. There is no need to be coy. A class should announce explicitly whether it is intended as an abstract class (in which case it should indicate provision for extensions), or a concrete class. The class diagram should identify data members, distinguishing between those that encode the state of an object and those that serve merely as links to independent collaborators. The member functions should be separated into those that form part of the public interface (the services provided by the class) and the "housekeeping" routines (which may be private or "protected"). If there are shared class data and/or functions, these should be indicated separately. Friendship relations, if any, can be shown as external entities that penetrate a class's boundary. (Class data, class methods, and friend relations are usually added in the later detailed design phase.)
Each class has a substantially extended Class-Responsibility-Collaborations "card" (more like a short essay than a "card"). Figures 11.10 and 11.11 illustrates a form that might be suitable for a hypertext based documentation system. In a hypertext system, each of the entries on a card would act as an active link to detailed documentation. Thus, each data resource would link to information defining the data type and role of that resource. In printed documentation, these links would have to be represented by page number cross references.
The class should be identified as a design class (rather than an analysis class) and should indicate whether it is intended as a pure abstract class, a partially implemented abstract class, an instantiatable class with provision for further extension, or a concrete class. Hypertext links (or cross references in printed documentation) should connect the design details to analysis and implementation details.
The resources owned would be grouped by general type - expanded data members that form part of the space allocated to the object, owned resources that are separately allocated (and for which an object of this class has only a pointer data member), and links to independent collaborators (again, a class instance will have pointer data members for these). The detailed documentation specifying the data type and role of each data resource would also identify those that might need to be accessed by subclasses (this information will also be available from the class diagram where tag marks are used to denote a need for such access).
Housekeeping routines would "link" to specifications of their algorithms. By the time the design is finalized, the details on each member function will include a specification of its signature. As with the data resources, the detailed documentation for each member function will repeat information from the class diagram concerning any requirement for a function to be accessed by, or possibly overridden in subclasses.
Both housekeeping functions and service functions (Figure 11.11) should be grouped by role. Smalltalk actually has a language construct (protocols) that can be used to group related methods. In other programming languages, such grouping has no semantics, it simply helps make both design and code more readable.
Class data and methods, if any, are grouped separately from instance data and methods.
The finalized design scenarios and life-histories of typical objects should both be kept as part of the documentation and should be linked to the class description. It is worth the class description highlighting information regarding how instances of a class get created and destroyed and whether instances of the class create or destroy instances of any other class(es).
If an instance of a class has expanded data members and owns many separately allocated data resources, then it may be worth having a class composition diagram. This simply repeats, in more graphic form, the information in the extended CRC card, see Figure 11.12.
Figure 11.12 is a sketch for such a class composition diagram; it shows a class Table that has several (expanded) data members that are instances of class Rect and a few more that are instances of class TextItem. In addition, a Table owns a format record (a separately allocated object that is an instance of class Format) and a list of entries for its rows (as a separately allocated object that is an instance of class List). A Table also owns many other separately allocated TableEntry objects. These are not shown in Figure 11.12 (though they could be shown as attached via the frows list). Links to independent collaborating objects would be shown as pointer data members in the "Expanded components" part of the diagram.
The design scenarios provide the real data that describe class collaborations. However, some summary documentation is also required. Many books on OO analysis and design include diagrams purporting to show such collaborations. These diagrams show graphs with the nodes representing instances of different classes and the arcs representing edges. In a real program, there are so many nodes, many joined by multiple directed edges, that a single diagram becomes impossibly complex.
In a large system, many "class collaboration" summary diagrams will be required. One approach was illustrated for the FSM program in Figure 10.18. A set of class collaboration diagrams is used; each diagram focuses on a specific class and shows the classes from which it requests services and the classes to which it provides services. Each of the links shown on the diagram has to be annotated with a list of all the services; there should also be cross references to design scenario diagrams. Naturally, there is redundancy among the class collaboration diagrams that are produced (a class will appear as a client or as a server in the diagrams for all the other classes with which it interacts). Figure 11.13 provides a further rather similar illustration of the type of diagram proposed; this figure is a (highly simplified!) view of a collaboration diagram focussed on class ChemStructure. (Again, there are advantages in using a hypertext system. Popup menus can be associated with each arc that list all the requests one class might make on another; selection of an entry from a menu links to a description of that interaction. )
Inheritance hierarchy diagrams will be required as part of the documentation. These will be similar to many shown before, such as Figures 10.12 - 10.15 that formed part of the documentation for the FSM program. It is not necessary to document the inheritance of concrete classes taken from a library (see Figure 10.15). There is no need to know such details for standard things like NumberTexts, Scrollers etc, as instances of these classes are simply employed to do a job. It is quite democratic; provided that you do you work right, no one will question your ancestry.
Some of the schemes proposed for documenting OO programs allow diagrams that combine data defining class inheritance, class composition, and class collaborations. This is simply not worthwhile. In any real program, each of these aspects is individually complex. Overlaying different aspects in a single diagram simply obscures the relations that a diagram is supposed to highlight.
Even if given inheritance hierarchies, class diagrams, composition diagrams and collaboration charts, a maintenance programmer may still be quite justified in complaining "I understand the classes, but not the program". The forms of documentation that focus on classes provide a limited, static view of a program's complex and dynamic behaviour.
Some guide to a program's dynamic behaviour is provided through the object life histories and the design scenarios. It is essential that the designers prepare these carefully so that the system can be made intelligible to maintenance programmers.
A class does not have to be completely defined before implementation begins because the responsibilities of a class can often be factored into distinct aspects. Some aspects can left unimplemented in partial implementations of a program. For example, with the chemistry editor, the development team could go ahead and implement the interactive parts of the structure editor long before the design of the algorithms for ChemStructure::ConvertToStandardForm() had even begun to be researched. A partial implementation keeps the client enthused (and even may help keep the development team enthused), and it can also serve as a framework for testing out ideas for code that will deal with the difficult bits of the problem.
However, before even a partial version of a class starts being coded up, the designers must resolve some issues. The following points are simply reminders of things that designers should be certain to cover before implementation work starts on a class. Most are already covered in the forms suggested for design documentation. Several points are C++ specific.
The designers may also be able to specify preconditions and postconditions for each method. These can be used to guide implementors and may also be used in automated systems for checking correctness.
An OO program will often have many such resource manager classes that are never expected to be used in assignments. Assignments involving things like Windows are rare! It may not be worthwhile implementing support for assignment. A class can simply be documented as not supporting assignment.; but, it may be wise to get the C++ compiler to enforce such a restriction. One can declare an assignment operator=() function, and a copy constructor, as private members of a class (while not providing any implementation) The compiler will then prevent any mistaken attempts to assign instances of such classes in code where they are used.
Of course, if a member function returns a pointer to a data member, the returned type should be const (because otherwise the encapsulation on the object can easily be breached). For example, if class Player does have a char* name data member, the member function that gives access to this name should return a const char*:
class Player {
public:
Player();
~Player();
...
const char* Name() const { return fname; }
...
private:
char* fname;
...
};
The return of even a const pointer weakens encapsulation (the "const"-ness can be type cast away exposing the structure). So be cautious.
Memory leaks, resulting from a failure to release space associated with discarded objects, are common in implementations of OO programs. A program with a memory leak may seem to run correctly; but, sooner or later, the program will mysteriously run out of heap space and fail during full scale use.
Some debugging aids and environments can help the implementor find leaks. For example, the ET++ memory allocator (which substitutes special implementations for C++'s standard new and delete operators) effectively keeps track of all objects that were created and can generate a report identifying those objects that were not properly freed before a program terminates. However, it shouldn't really be the responsibility for the implementors to have to find and plug leaks; the designers should have come up with a no leak system.
The class designs should map easily into C++ class declarations. The algorithms for the methods should be complete so that implementation involves simply some straightforward coding. The most common run-time problems with OO programs seem to relate to missed connections - one object tries to communicate with another but its pointer is incorrect and it calls a non-existent collaborator. But, if the designers have followed through object life histories, they should have covered all the ways in which objects can be created and then get linked to their collaborators - there should be no missed connections.
The implementors will utilize many simple, concrete classes taken from class libraries. These concrete classes serve simply to package standard data structure and provide a few useful routines for utilizing such structures. Favourite examples are a class Date that packages date-and-time services such as are commonly provided by an operating system, or a class Point that packages the concept of a point in 2-space. The use of such classes in the implementation should be noted in an appendix to the design level documentation.
For example, the company that developed Sue's chemistry editor might then start on a project that displayed drug molecules and drug receptor sites. Such programs are used in pharmacology research where drug-"designers" try to imagine molecules that are likely "fit into enzymes" etc. and so have desirable therapeutic properties; if a molecule looks the right shape, it can be synthesised and tested. Obviously, such a program uses "ChemStructures" - but they aren't quite the same, because in the second program the focus is more on three dimensional shape and the options for displaying and viewing structures. Still another vision of the "ChemStructure" abstraction would emerge from a project that aimed to create a reaction library that could be used to document known chemical transforms (and which would be used by the synthetic chemists to plan the synthesis of a molecule that was expected to be useful as a drug). A reasonable view of what a reusable "ChemStructure" component would only emerge after such accumulated experience. Once this generalized "ChemStructure" and related components had been identified, the company would start to get a useful specialized class library for further developments in this narrow vertical domain.
Such developments can be worthwhile. For example, data reported by Love show that Hewlett-Packard was eventually able to obtain a very significant amount of code reuse. This came in a series of products in the biomedical area. The software products controlled and analyzed the results from scanners and other bio-analysis devices. The common classes that were abstracted included user interface elements (specialized "dials" and other displays that showed technicians the states of the devices, and associated visual control elements that provided control over the devices), together with various classes for storing and manipulating image data etc. In some of the later software products developed for this domain, only 8% of the code was really new - the remaining 92% consisting of reused classes. Such results are probably somewhat exceptional; figures of around 50% reuse seem to be more common.
A company must provide resources for any class library project. It is after all an investment - one that will hopefully pay off in reduced development costs of future products. Budgeting the development is difficult - to what project should the initial costs be charged? The development will involve a class library group; initially, this group can acquire expertise in the framework library or any other standard class libraries that are to be used in projects. Each product development team should include a member seconded by the library group; this person would advise on the use of existing components. At the completion of version 1.0 of a product, a member of the development team should be transferred to the library group to explore generalizations of the classes developed for that product.
There are additional managerial problems associated with the generalization of code to form a class library. A company will end up with a class library group that busies itself abstracting generalized reusable classes from the initial versions provided by the different product development projects, while at the same time the teams that created these products are continuing with extensions and other "maintenance" work. Naturally, the result is several diverging sets of "improved" classes. At some stage in the development of an in-house specialized library, management may wish to have existing products retrofitted with the library classes instead of the original classes. Management is likely to find some interesting challenges in accommodating such work into limited "maintenance" budgets, and in dealing with personnel problems such as developers who feel proprietorial about their work.
Brooks: There is no single development, in either technology or in management technique that promises even one order-of-magnitude improvement in productivity, in reliability, in simplicity.
On the data reported by Love, Hewlett-Packard once did get an order of magnitude improvement in a project. OK, that result is abnormal and the measure (only 8% of the code for a project being new) doesn't really indicate the true cost so the real productivity gain would have been lower. But, going object-oriented certainly has some benefits, these can include:
On a longer term basis, beginners will need to read widely on ideas for OO analysis and design. If the design is correct and contains sufficient detail, implementation is easy. The important skills to acquire from further study are in OO design.
Stroustrup's text contains some suggestions on OO design. Bar-David's book illustrates design suggestions with some examples, but the treatment is not really much deeper than that in this chapter and in Chapter 10. There are also some suggestions on design in Meyer's book on OO software construction.
The books by Wirfs-Brock et al., and by Henderson-Sellers are probably the starting points for further reading. The approach in the Wirfs-Brock book may seem a little naive in places (it is the approach of underlining nouns etc. to identify objects) but the book does provide an easily read overview of OO design. The Henderson-Sellers book is essentially a printed version of a one day industrial tutorial on OO, focussing mainly on analysis and design. It comes complete with printed versions of the overheads from the tutorial. The book offers a cheap way of gaining the benefits from such a course.
The approach taken by Coad and colleagues (three books, OOA, OOD, and OOP) is quite different from that suggested in this chapter. In Coad's approach, classes come first. (Classes appear in Chapter 3 of the analysis book where one finds Classes-&-Objects. Class hierarchies and compositional structures are resolved in Chapter 4. But, it is only in Chapters 6 and 7 that one starts to look for the attributes and services provided by these already identified classes.) A notational system, of some subtlety, is proposed; this notation is supported through a commercially available tool that allows boxes, arrows and labels to be combined to document the results of the analysis and design processes. Programs are expected to involve a "domain component", "a human interaction component", "a task management component", and "a data management component". The OOD volume covers design issues related to these separate components. The OOP book has a series of increasingly complex examples that are each implemented using both Smalltalk and C++.
Booch's original (1991) book Object-Oriented Design with Applications covers concepts, "method", and applications. The "method" section presents another notational system; this one is intended for documenting designs (and, again, it is supported through commercially available design tools). The notational system may be suited to use in major systems, but was too complex and subtle for use in an introductory text like this (e.g. there are ten different kinds of arrow with distinct meanings determined by the form of the arrowhead and the style of shaft). The "method" section also covers pragmatics (essentially issues of project management) and the process. The description of the OO design process is a bit brief. The applications section contains detailed examples illustrating design ideas in the context of different applications. Each example application is also used to illustrate a particular implementation language - Smalltalk, C++, Object Pascal, CLOS, and Ada (for an object-based rather than object-oriented example). A second edition expands to cover analysis and provide more detail on the process and pragmatics of design. The example applications in the second edition all use C++.
The book by Goldstein and Alger is worth reading at some stage. (The bit in the book's title about "... for the Macintosh" is misleading; the only Macintosh specifics are a few screen dumps and the use of the word Macintosh where others would use computer. This OO design book was published as part of the Inside Macintosh series, so some attachment to the Macintosh was probably a requirement.) This book covers some higher level analysis issues. For example, it goes into the need for the analysts to document the clients' existing problem solving processes and to provide a mapping from these to the proposed computer-based approaches; such things being necessary to plan retraining and to develop documentation. Somewhat less sanguine than the other texts, this book explores some of the problems with OO (like why it is often difficult to find the right classes and why one's "classes" often turn out to be categories that are of no use in design and implementation). A methodology - "Solution Based Modelling" - is proposed. The methodology works in terms of "planes" - the "business plane", the "technology plane", the "execution plane", and the "program plane". The business plane describes the problem in terms of the existing and proposed solutions; the proposed solution is mapped into the technology plane where the user interface and main run-time objects get described. The execution plane presents more detail on relations among objects, calling sequences etc. The book introduces the idea of "calibration" (essentially, a way of tying together concepts and their implementation and providing traceability). Goldstein and Alger make heavy use of scenarios that map out interactions among objects, with these scenarios forming a major part of the design documentation. Another notational system is presented (again, available as a commercial package); this system explicitly supports the sketching of scenarios for object interactions.
Berard has published a set of essays on aspect of OO and software engineering. Some review different methodologies; others attempt to clarify ideas such as "object cohesion" or the differences between "encapsulation" and "information hiding".
The books by Rumbaugh et al. and by Jacobson et al. are both a bit more advanced. These books present work of industrial developers, and both are based on years of extensive practical experience. Jacobson's "use cases" are related to the scenarios suggested in this chapter, or those of Goldstein and Alger.