The following is a collection of notes taken from Designing Object-Oriented Software. This is a good book about OOD, but a bit dated. The follow up book is more interesting, Object Design, which goes more into object roles, responsibilities and collaborations. Still there is wisdom, in particular I liked the emphasis on objects as agents – the dice rolls, you don’t roll the dice.
Chapter 1 – Why use Object-Oriented Design?
Software design is an art: discipline, hard work, inspiration, and sound technique all play their parts. Responsibility-driven design is a coherent model for the design process. Software applications are complex because they model the complexity of the real world.
Abstraction is a natural mental process
People typically understand the world by constructing mental models of portions of it. A mental model is a simplified view of how something works so that you can interact with it. In essence, this process of model-building is the same as designing software, but software development is unique: software design produces a model that can be manipulated by a computer.
Abstraction is the key to designing good software.
OOD decomposes a system into objects, the basic components of the design. Objects may be modeled on inanimate or even conceptual entities in the real world, but within their systems they act as agents. In the real world, objects do not dial each other, nor colors paint themselves, without human agency. Whereas the systems of daily life require human agents to make things happen, objects are the agents within their own systems. OOD encourages a view of the world as a system of cooperating and collaborating agents.
Chapter 2 – Objects and Other Basics
OOD attempts to manage the complexity inherent in real-world problems by abstracting out knowledge, and encapsulating it within objects. Finding or creating these objects is a problem of structuring knowledge and activities.
We are used to dividing information into two distinct kinds: functions and data.
OOD initially starts with a more abstract focus. It asks first about the intent of the program, the what, not the how. Its goals are to find the objects and their connections by determining what operations need to be performed and what information results from those operations. Each object knows how to perform its own operations and remember its own information. OOD composes a system into entities that know how to play their roles within the system, structuring responsibilities by asking “What can this object do?” and “What can this object know?”
Objects which share the same behavior are said to belong to the same class. A class is a generic specification for an arbitrary number of similar objects. You can think of a class as a template for a specific kind of object, or as a factory, cranking out as many of its products as required.
A class allows you to build a taxonomy of objects on an abstract, conceptual level. Classes allow us to describe in one place the generic behavior of a set of objects, and then to create objects that behave in that manner when we need them.
All objects are instances of some class. Once an instance of a class is created, it behaves like all other instances of its class. However, it receives a copy of the information declared instance specific. This is known as the object’s state. Modifying this information does not affect other instances of the class. Each object maintains its own state. For example, you can have two instances of a pen class, one is red the other blue (state), both can print text on the screen (behavior).
The act of grouping into a single object both data and the operations that affect that data is known as encapsulation. Encapsulated knowledge can be hidden from external view. An object has a public face which it presents to other objects within the system, consisting of what it can do and tell. Thus, other entities know only how they can interact with it. The private side of an object is how it performs its operations or computations, this is known as information hiding. An object is free to change its private side, but not its public.
Polymorphism is the ability of two or more classes of an object to respond to the same message, each in its own way, achieved via inheritance.
Inheritance is the ability of one class to define the behavior and data structure of its instances as a superset of the definition of another class or classes.
A subclass (child) is a class that inherits behavior from another class (inheritance). A subclass usually adds its own behavior to define its own unique kind of object.
A superclass is a class from which specific behavior is inherited (parent).
Classes that are not intended to produce instances of themselves are called abstract classes. They establish an interface which subclasses must implement. They typically contain code for generic methods. Some languages, like C++, do not have interfaces. The abstract class is the classic alternative.
The process of OOD is exploratory. The designer looks for classes, trying out a variety of schemes in order to discover the most natural and reasonable way to abstract the system:
- Find the classes in your system
- Determine what operations each class is responsible for performing, and what knowledge it should maintain
- Determine the ways in which objects collaborate with other objects in order to discharge their responsibilities
These steps produce:
- a list of classes within your application
- a description of the knowledge and operations for which each class is responsible, and
- a description of collaborations between classes
With his information, you can begin the analytical stage of the design process:
- Look for inheritance relationships between classes
- Look at the behavior of each class
- Are there classes that share certain responsibilities?
- Can you factor out these shared sets of responsibilities into super-classes?
Next, analyze the collaborations between the classes in an effort to streamline them:
- Are there places in the system where the message traffic is heavy?
- Are there objects which collaborate with everybody?
- Are there object which collaborate with nobody?
- Are there groups of classes that work close together?
The analysis of your preliminary design consists of:
- factoring common responsibilities in order to build class hierarchies, and
- streamlining the collaborations between objects.
Subsystems of Classes
A subsystem is a set of classes collaborating to fulfill a set of responsibilities.
Clients and Servers
Collaborations are viewed as one-way interactions: one object requests a service of another object. The object that makes the request is the client, and the object that receives the request is the server. The ways in which a given client can interact with a given server are described by a contract. A contract is the list of requests that a client can make of a server. Client and server are roles.
Chapter 3 – Classes
You start with the requirements specification, it is the only input you have. If you don’t have a specification of what you are to design, write down a description of the goals of your design. Your goal is to create classes of objects that will model the domain of your application. Items mentioned in the specification are likely to be part of that model. Specifically, look for noun phrases. Change all plurals to singulars, and make a preliminary list.
Choosing the classes in your system is the first step in defining the essence of your problem. If you can name an abstraction, you have found a candidate class. If you can formulate a statement of purpose for that candidate class, the chances are even higher it will be included in your design. Emphasize the important, and eliminate the irrelevant.
- Model physical objects, such as disks or printers
- Model conceptual entities that form a cohesive abstraction, such as a window, or a file
- Eliminate competing synonyms – select the best, remove the rest
- Be wary of adjectives, focus instead on nouns – i.e. Point rather than Start Point and Stop Point
- Be wary of sentences in the passive voice, or those whose subjects are not part of the system
- Model categories of classes
- Model known interfaces to the outside world
- Model the values of attributes of objects, but not the attributes themselves
When you have identified your candidate classes, write their names down on index cards, one class per card. On the back of the card, write the overall purpose of the class.
Find Abstract Classes
Re-examine your list of cards to identify as many abstract classes as possible. Identify these classes in order to help identify the structure of the software. An abstract class springs from a set of classes that share behavior and attributes. Identify candidates for abstract superclasses by grouping related classes. When you have identified a group, name the superclass that your feel it represents. Make the name a singular noun or noun phrase.
When superclasses have been identified, record them on index cards, one class per card. Also write its subclasses on the lines below the name. Go back to all your class cards, and record their superclasses and subclasses, if any are known, on the lines below their names.
If you are having trouble naming a group you have identified:
- enumerate the attributes shared by the elements of the category, and derive the name from those attributes, or
- divide the elements into smaller, more clearly defined categories
If you still cannot name it, discard the group and search for others.
Some common attributes by which classes can be grouped are:
- Physical vs Conceptual: Display Screen, Windowing Transformation
- Active vs passive: Producer, Product
- Temporary vs permanent: Event, Event Queue
- Generic vs specific: List, Event List
- Shared vs unshared: Print Queue, Login Name
Discard attributes if they do not help distinguish between classes.
Identifying Missing Classes
Classes can be missing because they are not important, or because the specification was imprecise.
Chapter 4 – Responsibilities
What are responsibilities?
- the knowledge an object maintains, and
- the actions an object can perform.
Responsibilities are meant to convey a sense of the purpose of an object and its place in the system. The responsibilities of an object are all the services it provides for all the contracts it supports. A service can be either the performance of some action, or the return of some information. Responsibilities are intended to represent only publicly available services. Focus on the contract.
Responsibilities can be identified in a number of ways. At the start, two of the most fruitful sources are the requirements specification and the classes you have already identified.
The Requirements Specification
Highlight all the verbs, and use your judgment to determine which of these clearly presents actions that some object within the system must perform. Similarly, everywhere information is mentioned, note it. Information that some object within the system must maintain and manipulate also represents responsibilities.
One of the most useful ways to find responsibilities is to perform a walk-through of the system as a whole. Imagine how the system will be invoked, and go through a variety of scenarios using as many system capabilities as possible. Look for places where something must occur as a result of input to the system.
Also use the work you have already done when you identified the classes. The name you chose for a class suggests a responsibility, and possibly others. These names represent roles within your system; they imply responsibilities for the objects that must fulfill those roles. The statement of purpose for each class may also contain additional responsibilities. Comparing and contrasting the roles of classes can also generate new responsibilities.
Assign each responsibility you have identified to the class or classes it should logically belong to.
- Evenly distribute system intelligence
- State responsibilities as generally as possible
- Keep behavior with related information, if any
- Keep information about one thing in one place
- Share responsibilities among related objects
When designing classes, you must decide how to distribute intelligence among them. Distributing the intelligence embodied within your system among a variety of objects allows each object to know about relatively fewer things. This produces a more flexible system, one that is easier to modify. If a class seems to have an unduly long list, stop and re-examine it.
Examining Relationships Between Classes
Additional responsibilities can be identified by examining the relationships between classes. Once you have a good idea of the classes that compose your system, examine the relationships between them to gain insight in assigning their responsibilities:
- the “is-kind-of” relationship;
- the “is-analogous-to” relationship, and
- the “is-part-of” relationship.
When a class seems to be a kind of another class, it is frequently a sign of a subclass-superclass relationship between them. Such as relationship is often pointed out by the fact that the two classes share an attribute.
If class X bears a relationship to another part of the system that is analogous to that borne by class Y, then perhaps class X and Y share the same (or analogous) responsibilities. Perhaps they are both kinds of something else, an as-yet-unidentified superclass.
A clear distinction between whole and part may help us determine where responsibilities for certain behavior ought to be, mainly by narrowing the scope of possibilities. An object composed of other objects must always know about its parts.
If you are having difficulty assigning a particular responsibility to one class or another, choose a path and walk through the system to see how it feels. Sometimes you must make decisions, even when the issues may not be clear to you. Experiment in the early stages of design.
On each class card you have created, record each responsibility assigned to that class. List the responsibilities as succinctly as possible.
If you have created one all-knowing, centralizing class, you should carve it up into several cooperating classes.
Chapter 5 – Collaborations
How do classes fulfill their responsibilities? They can do so in two ways: by performing the necessary computation themselves, or by collaborating with other classes.
By identifying the collaborations between classes, seemingly disparate classes can be connected.
Collaborations represent requests from a client to a server in fulfillment of a client responsibility. A collaboration is the embodiment of the contract between a client and a server. It may take several collaborations to fulfill a single client responsibility. The pattern of collaborations within your application reveals the flow of control and information during its execution.
In order to determine collaborations between classes, begin by analyzing the interactions of each class. Examine the responsibilities for dependencies. To identify collaborations, ask the following questions for each responsibility of each class:
- Is the class capable of fulfilling this responsibility itself?
- If not, what does it need?
- From what other class can it acquire what it needs?
Each responsibility that you decided to share between classes also represents a collaboration between those classes.
Similarly for each class ask:
- What does this class do or know?
- What other classes need the result or information?
- If a class has no interactions with other classes, it should be discarded
Collaborations are associated with a responsibility, Collaborations do not exist except to fulfill a responsibility. To record the collaborations you have found for the class playing the role of the client, write the name of the class playing the role of the server directly to the right of the responsibility being served. If a responsibility requires several collaborations, write the name of each class required to fulfill the responsibility.
Chapter 6 – Hierarchies
A contract represents a cohesive set of responsibilities.
You can make a class hierarchy easier to refine by maximizing the cohesiveness of the contracts supported by the classes within it. Just as a contract should be composed of a cohesive set of responsibilities, a class should support a cohesive set of contracts.
Maximizing cohesion will tend to minimize the number of contracts supported by each class. The fewer contracts, the more easily new subclasses can be constructed from the building blocks provided.
A good design balances the goal of small, easily understood and reused classes with the conflicting goal of a small number of classes whose relationship with each other can be easily grasped. The principle of cohesion helps you strike that balance.
If a class defines a contract that has relatively little in common with the rest of the contracts defined by that class, it should be moved to a different class, usually a superclass or a subclass.
One key to testing and maintaining your design is its comprehensibility. Your system will be more comprehensible if there are fewer details to comprehend.
Chapter 7 – Subsystems
Subsystems are groups of classes, or groups of classes and other subsystems that collaborate among themselves to support a set of contracts.
A subsystem should form a good abstraction.
Subsystems are a concept used to simplify a design. The complexity of a large application can be dealt with by first identifying subsystems within it. A subsystem is a conceptual entity.
When you have identified subsystems, write their names down on index cards, one subsystem per card. Add a short description of the overall purpose on the back of the subsystem card. Record each contract required by clients external to the subsystem. Beside each contract, record the delegation to the internal class or subsystem that actually supports the contact.
When you have identified subsystems, go back to the class cards and modify their collaborations to reflect these changes. If a class outside a subsystem collaborates with a class inside the subsystem, change this to a collaboration with the subsystem.
Subsystems simplify patterns of communication.
The basic guidelines for simplifying the patterns of collaboration are:
- Minimize the number of collaborations a class has with other classes or subsystems
- Minimize the number of classes and subsystems to which a subsystem delegates
- Minimize the number of different contracts supported by a class or a subsystem
Chapter 8 – Protocols
This chapter is essentially about documenting method signatures, and the purpose of the method in relation to a responsibility. It is a refinement, and a specialization.
You write design specifications for each class and subsystem, and for each contract.
Chapter 9 – Implementing Your Design
Essentially runs through selection of language, tooling, and other factors related to the implementation of the design. Discusses type checking, inheritance etc.
Chapter 10 – Another Design
A case study, walking through the process illustrated in the book.