Responsibility-Driven Design - Responsibility-Driven Design - How to Use Objects: Code and Concepts (2016)

How to Use Objects: Code and Concepts (2016)

Part IV: Responsibility-Driven Design

Chapter 11. Responsibility-Driven Design

Design can be a rather elusive subject. Very often, one meets people who claim that some design is “elegant” or “crap,” as the case may be, and who seek to substantiate that claim with rather abstract concepts such as “loose coupling” or the need for “shotgun surgery,” respectively. It is not uncommon to have heated debates about, and almost religious beliefs in, certain design approaches.

From a more practical, down-to-earth point of view, design can be understood just as structuring the solution to a problem. From this perspective, we all design every day because we simply cannot avoid it. Before we start typing out any code, we make a plan, however briefly, about what we are going to type. For common problems, we reexecute time-proven plans from our existing repertoire. For larger problems that we have not solved previously, we may stand back and discuss alternative strategies, either by ourselves or within our team. For really long-term goals, we may take the time to explore existing systems and solutions.

Design is necessary because the solution to any practical problem is usually so complex that we, as human beings, cannot cope with all of its details at once. We have to summarize and abstract over some aspects to focus on and think clearly about others. The design helps us to place the current aspect into the larger picture. It keeps the overall software in shape while we work on a specific class, method, or internal data structure.

But design does not stop at creating just any solution. Usually, we look for a solution that is particularly “good” in some sense. It should be as simple as possible to reduce the implementation effort. At the same time, it may need to allow for easy extensions by new functionality or it may have to be reusable in several products. Necessarily, such extra demands also cost extra implementation effort, and this is where the disputes begin: Which design will offer the best cost/benefit ratio? Which one will be advantageous in the long run? Since such questions can be answered only with hindsight, we often have to rely on experience. Unfortunately, humans tend to prefer their own experience to that of others—quite often, squabbles about design are just another case of the not-invented-here syndrome.

To create and discuss designs, we have to create a suitable language for the task. Saying that objects contain data and the associated operations is certainly correct, but it remains close to the implementation. As a result, it becomes very hard to forget one object’s details to focus on another object. There are many approaches to object-oriented design and they all come with their own terminology—the language that we choose tends to shape the way we think about a given problem.

Image 32,264,55,263,262 For this book, we have chosen responsibility-driven design, because it has been very influential to the point where its terminology is now found throughout the literature on software engineering. The presentation is split into two chapters. This chapter gives an overview of designing with responsibilities. After introducing the core concepts in Section 11.1, we look more closely at the central strategy of assigning one main responsibility to each object in Section 11.2. To get more details, we then create a self-contained example application, a plotter for function graphs, in Section 11.3. Having mastered the technicalities, we finish by looking at a few indispensable strategies that help to find “good” designs in Section 11.5. While this chapter keeps at the level of individual objects and small groups of objects, the next chapter will look at building blocks at the architectural level and at more abstract design strategies.

11.1 The Core: Networks of Collaborating Objects

We started our investigation of object-oriented software development withImage 1.1 the central assumption that objects are small and cheap entities, so that we can have as many as we like to solve a given problem. Having explored and mastered the technical details of the language, design-by-contract, and event-driven software, we are now in the position to ask: Which objects do I create to solve a given task? Which methods do they have?

Of course, it is very hard to come up with general recipes: Software development is and probably will always be a creative activity that involves experience, decisions, and sometimes even taste (although the last facet is grossly overrated—often it is merely a way of referring to one’s accumulated experience without being able to pinpoint specific incidents or examples).Image 263,261 The literature gives many good guidelines on finding objects. They are most helpful when one already has some previous design experience. To gain this experience quickly, we will look at the design of Eclipse’s New ClassImage 100 wizard: To become a good designer, there is nothing like imitating experienced professionals.

The presentation in this section proceeds in three steps. First, we look at the objects and their behavior in a concrete usage scenario to understand the overall approach. Second, we analyze the principles underlying the design. Finally, we examine a few general guidelines about the design process itself.


Software is a network of collaborating objects.


Let us plunge into the topic of design with an everyday example. Eclipse’s New Wizard in Fig. 11.1(a) is reachable through the New/Other . . . entries of the Package Explorer’s context menu. (It is shown with indications of the internal structure for later reference.) The user can create virtually any kind of relevant file, complete with a sensible initial content. We will consider the example of creating a new Java class. When we select the Class entry in Fig. 11.1(a), the content of the dialog changes to the well-known class creation wizard in Fig. 11.1(b). The use case is simple enough, but how does the New Wizard accomplish the task?

Image

Figure 11.1 The New Wizard

When we look at class NewWizard, we find that it has something like 170 lines of code, including whitespace and comments. So all the complex functionality must reside somewhere else, and the NewWizard is only one piece in a larger design that provides the required functionality.

Digging through the different classes and objects involved reveals the structure shown in Fig. 11.2. Admittedly, it seems a bit complex, but the nine objects between them must accomplish the overall task of creating a new Java class through the dialog in Fig. 11.1. We will say that the objects collaborate toward that common goal. Collaboration usually consistsImage 264,32,109 of simple method calls. To enable these calls, each object holds references to relevant other objects, so that the objects form the network (or graph structure) shown in Fig. 11.2. The idea of a network here also encompasses temporary references passed as parameters or stored in local variables.

Image

Figure 11.2 Structure of the New Wizard

We will now explain Fig. 11.2 briefly and will also link the explanation to the sources. The subsequent analysis of the underlying principles will justify the perceived complexity. We encourage you to refer back to Fig. 11.2 throughout the explanation, to validate the connections in detail.


The design must be traceable to the implementation.


It is a general principle that a good design is truly valuable only if it isImage 11.5.4 also reflected in the code. After all, if the design does not explain how the code works, why bother with design in the first place? The term traceabilityImage 7,24 covers such relationships between different artifacts in software engineering processes.


Design creates mechanisms in which each object has its place.


To explain the overall network, it is best to follow the steps of the New Wizard chronologically, along the user experience. At the top of Fig. 11.2, theImage 9.3.4 NewWizardAction can be placed in the menu or the toolbar. When clicked, it creates a generic WizardDialog, which is built to contain any kind of IWizard. Leaving out the irrelevant code, the action’s run() method is this:

org.eclipse.ui.actions.NewWizardAction


public void run() {
NewWizard wizard = new NewWizard();
Shell parent = workbenchWindow.getShell();
WizardDialog dialog = new WizardDialog(parent, wizard);
dialog.open();
}


Image 9.3The framework of generic wizards is provided in the JFace layer. A JFace Wizard object is basically a container for pages and manages the switching between these pages. It also contains the code that gets executed once the user clicks the Finish button, in a methodperformFinish(). Each page can check whether the current entries are admissible and complete; also, each page is contained in one wizard, and it can dynamically compute the next page. The interface IWizardPage captures just this design:

org.eclipse.jface.wizard.IWizardPage


public interface IWizardPage extends IDialogPage {
public boolean canFlipToNextPage();
public boolean isPageComplete();
public IWizard getWizard();
public IWizardPage getNextPage();
. . . simple properties for name and previous page
}



Image Image 3.2.7The interface IWizard abstracts over the Wizard class to to decouple subsystems. Almost all concrete wizards in the Eclipse platform derive from Wizard.


The NewWizard is one particular wizard and extends Wizard. It organizes the overall process, as seen in its addPages() method in the next code snippet. First, it requests all available kinds of new files. Internally, these are termed—unfortunately—“new wizards,” because technically they are, again, wizards. In the current context, they are represented by objects implementingImage 1.3.8 IWizardDescriptor, which are held in a singleton NewWizard RegistryImage 12.3.3.2 (which in turn collects them as extensions to a special extension point in the Eclipse IDE). The NewWizard contains a single page New WizardSelectionPage, which displays the available wizard types, as seen in Fig. 11.1.

org.eclipse.ui.internal.dialogs.NewWizard


public void addPages() {
IWizardRegistry registry =
WorkbenchPlugin.getDefault().getNewWizardRegistry();
IWizardCategory root = registry.getRootCategory();
IWizardDescriptor[] primary = registry.getPrimaryWizards();
. . .
mainPage = new NewWizardSelectionPage(workbench,
selection, root,
primary, projectsOnly);
addPage(mainPage);
}


The NewWizardSelectionPage delegates the actual display to a helper class NewWizardNewPage. It creates that helper when it sets up its displayImage 7.1 in the createControl() method:

org.eclipse.ui.internal.dialogs.NewWizardSelectionPage


public void createControl(Composite parent) {
newResourcePage = new NewWizardNewPage(this, wizardCategories,
primaryWizards, projectsOnly);
Control control = newResourcePage.createControl(parent);
. . .
}


The crucial interaction occurs when the user selects a particular wizard from the list and clicks the Next button. At this point, the NewWizard asks the IWizardDescriptor to create a wizard—in the current scenario a NewClassCreationWizard, which has a singleNewClassWizardPage (see Fig. 11.2). All of this happens behind the scenes of the NewWizard Registry. Since that class is all about the larger goal of extensibility, we postpone its treatment and get on with the work: All that matters now isImage 12.3.3.3 that we have aNewClassCreationWizard.

The link between the NewWizardSelectionPage and the selected follow-up wizard happens through the mechanisms inside WizardDialog. As seen in the interface IWizardPage, the dialog will ask each page whether it is OK to switch to the next page and what that page will actually be. The wizard selection page employs this mechanism to compute the desired page dynamically, from the selected follow-up wizard.

org.eclipse.jface.wizard.WizardSelectionPage


public boolean canFlipToNextPage() {
return selectedNode != null;
}


org.eclipse.jface.wizard.WizardSelectionPage


public IWizardPage getNextPage() {
IWizard wizard = selectedNode.getWizard();
if (!isCreated) {
// Allow the wizard to create its pages
}
return wizard.getStartingPage();
}



Image Image 1.4.1Both of these methods are inherited from a generic WizardSelectionPage, but this is irrelevant at this point.


We now quickly summarize what happens when the user clicks the Finish button, leaving out the code for brevity. First, the wizard dialog invokes performFinish() on its current wizard. That wizard may be either a New Wizard or a NewClassCreationWizard, depending on whether the user has already clicked Next. The first case merely delegates to the second, so the call ends up with the NewClassCreationWizard, which through several indirections invokes the following method. There, finally, fPage is a New ClassWizardPage (see Fig. 11.2), which performs the actual creation of the source file.

org.eclipse.jdt.internal.ui.wizards.NewClassCreationWizard


protected void finishPage(IProgressMonitor monitor)
throws InterruptedException, CoreException {
fPage.createType(monitor);
}



Image The alert and suspicious reader will note that overall setup for creating a class sourceImage 9.1 file actually breaks model-view separation: The code for creating the Java class is contained in the user interface plugin org.eclipse.jdt.ui. Since the class creation codeImage 12.1 is tightly linked to the options available on the wizard page, it would seem sensible to keep both together: If the options change, the code can easily change as well. The situation would be different if the Eclipse JDT included a full-fledged code generation component. That component would certainly have to be model-level, and then class creation would merely pick the right tools from the general functionality.


We have reached the end of this first part, in which we followed the workings of the class creation wizard once through the user experience. It has certainly proved to be a rather complex mechanism. Subsequently, we will see why the complexity is necessary and which general principles explain the chosen design.


Each object is a busy and efficient clerk in a larger machinery.


Even though the objects in Fig. 11.2 together accomplish the larger task of creating a new source class, the individual objects are not aware of this fact and do not have to be aware of it: Each one simply does its own job.Image 264,32 It is like a clerk who has a certain set of responsibilities. The overall “administration” is set up such that the machinery runs smoothly and all daily tasks are accomplished as long as each clerk fulfills its own responsibilities, or performs its own duties efficiently.

For instance, the NewClassWizardPage is responsible for creating a new source class, by first querying the user for the required parameters and then creating the source file. It does not have to be aware of the context of bringing up the pop-up dialog, selecting a file type, and so on. Indeed, it is this focus on its own responsibilities that allows the class to be reused asImage 12.4 part of several other wizards. Likewise, the WizardDialog is responsible for managing pages and enabling the user to switch between them. It does not care about the content of the pages, nor is it concerned with validating the data the user enters: Those aspects are the responsibility of single pages.


Image The idea of “clerks” and “responsibilities” may at first sound a bit too abstract to be helpful. But it is really just a metaphor: By saying that “an object is like a busy and efficient person,” we can transfer our understanding of busy and efficient persons to the behavior of objects. If we can imagine how a person would handle a task, we can better imagine how an intangible object would approach it. Metaphors are, in fact, very common in software engineering. For example, design-by-contract transfersImage 4.1 our understanding of legal contracts to method calls. A level further down, we perceive method calls and code execution as a “behavior” triggered by “messages” to an object. InImage 1.4.1 the area of network programming, a “socket” is a point for attaching network connections. The term “connection,” in turn, visualizes the perceived result of sending and receiving sequences of data packets using the TCP protocol. In short, we use metaphors to link the unknown, invisible software world to our everyday experience, thereby transferring our understanding of one to the other.



Some objects perform managerial duties.


We have used the term “clerk” to invoke the image of a diligent, efficient worker that goes about its daily tasks in a predictable manner. However, the tasks themselves need not always be humble or basically stupid. In fact, many objects display a great deal of “intelligence” in their implemented logic. Also, many objects are more like managers, although their behavior is still determined by strict rules. For instance, the NewWizard arranges for things to happen, but it does not do any of these things itself. Such objects will usually make decisions on behalf of and about other objects.Image 1.8.1


Roles are sets of responsibilities taken on by different objects.


Once we start thinking in terms of responsibilities, we quickly find that different objects can take on the same set of responsibilities in different situations. In the example, the WizardDialog can contain many kinds of wizards, but all of those wizards must fulfill the responsibilities defined by the interfaces IWizard and IWizardPage: The wizard must create and manage a set of pages; each page must perform validation and must compute the next page, and so on.

A role is a set of related responsibilities that can be fulfilled by differentImage 263,211 objects. At the code level, roles usually map to interfaces or abstract baseImage 3.2.10 classes, where the abstract methods capture the expected behavior of the objects, to be filled in later by the concrete classes. For the purposes of design, it is, however, better to postpone the definition of the interface and to focus on the conceptual responsibilities instead. That is, before deciding how a given service will be accessed, one has be very clear about what the service should actually be. Introducing too many implementation details too early tends to lead to premature restrictions of the design.

A role can serve as an abstraction over a number of special cases, such as for the wizards described earlier. The intuition is shown in Fig. 11.3(a), where the concrete collaborator of an object can be exchanged without theImage 12.3 object noticing. Roles then lead to extensibility, because new objects fittingImage 12.4 the role can be introduced at any time. They can also lead to reusability, because an object accessing a collaborator defined by a role can work with different collaborators in different scenarios.

Image

Figure 11.3 Illustration of Roles

Image 211 A second intuition of roles is that they create slices of an object’s behavior, as shown in Fig. 11.3(b). Usually a role does not prescribe the entire behavior of an object, but rather captures some aspect that is relevant in the current context. For instance, the NewClassWizardPageof the example contains all the logic for creating a new source file, yet the WizardDialog will be interested only in its ability as an IWizardPage: When the userImage 3.2.2 clicks the Next button, the dialog requests the next page. This view on roles links back to the idea of client-specific interfaces, which carve out a particular aspect of an object’s behavior for special usage scenarios.

org.eclipse.jface.wizard.WizardDialog


protected void nextPressed() {
IWizardPage page = currentPage.getNextPage();
if (page == null) { return; }
showPage(page);
}



Image Fig.11.3 might remind you of the illustration of the Adapter pattern in Fig. 2.10. In both cases, an object accesses a collaborator through a predefined interface. However, in the case of roles it is usually the object itself, rather than some artificial adapter, that provides the required methods. Also, the definition of the role usually predates that of the object. The link between the two situations is this: An adapter becomes necessary if an object fits the conceptual description of a role’s responsibilities, but not its technical realization as an interface definition.



Design decisions must be based on concrete goals.


Before we examine the process of designing, let us reevaluate the setup in Fig. 11.2. When we started, it certainly seemed unnecessarily complex: Why not create a NewClassCreationDialog, in a single class, and be done with it? We could even use the WindowBuilder to assemble this dialog in a fewImage 7.2 minutes! Why bother to invent so many objects and so many intermediate steps that it becomes hard to see how the overall task is accomplished?

The reason is very simple: The particular chosen distribution of responsibilities between objects attains several goals, which together make the Eclipse code base as a whole less complex, more maintainable, and more extensible.

• As a pervasive goal, the individual classes remain small because eachImage 11.2 object deals with only one main task. As a result, the maintenance developer has to understand less code when trying to debug or modify some functionality.

• At the same time, the objects have disjoint responsibilities, so that theImage 11.5.2 maintenance team knows where to look if some aspect of the behavior needs fixing.

• The WizardDialog is a generic piece of infrastructure, which can beImage 12.4.2 reused in many places. A special-purpose dialog assembled with the WindowBuilder could be used only once.

• The NewWizardRegistry takes care of gathering contributedImage 12.3.3.3 wizards for quite diverse file formats and other artifacts, such as example projects, from throughout the Eclipse platform. New plugins can offer new types of files by simply implementing the interfaceINewWizard and registering the implementation with extension pointImage 12.3.3 org.eclipse.ui.newWizards.


Image Properties such as extensibility and reusability characterize aspects of a software’s internal quality that are quite independent of the quality perceived by the users. Nonfunctional properties (or nonfunctional requirements) capture precisely those aspectsImage 24 Image 5.4.6 of a system that are not covered by the use cases, which describe the system’s reactions to external stimuli. Other nonfunctional properties include the perceived speed of the answers or the stability of the system. Many of these properties cannot be addressed byImage 24,59,218 software design alone, but fall into the realm of software architecture.


Design must always be driven by such concrete demands; it is never an aim in itself. If someone tells you that his or her approach is simply “more elegant” or “cuter,” be deeply suspicious: Chances are that the person is trying to sell you an overly complex solution that does not yield benefits in return for the greater implementation effort. Another trap is to makeImage 12.4 objects “reusable” by introducing “general” mechanisms right from the start, even if no reuse is planned for the foreseeable future and no one knows as yet the details of a possible reuse scenario. In all such situations, simply insist on evaluating the concrete mid-term savings of the approach. A good guideline is the “rule of three”: You should have three concrete applications before setting out to define an abstraction. If the answer to these questionsImage 1.2.2 is insufficient, placate the team by pointing out that refactoring can alwaysImage 92 be done later to implement their ideas. Avoid speculative generality to stay productive.


Image Unfortunately, the human mind—and in particular the developer’s mind—likes complex and creative solutions: When confronted with a simple everyday problem that looks rather dull, why not spice it up with a bit of intricate design? Try to avoid this temptation as far as humanly possible.



Objects know information, perform tasks, and make decisions.


We have introduced the approach of responsibility-driven design and have explored the need for explicit designs that also anticipate later reuse and changes. How, then, does one create such designs?

Image 1.8.1We have already examined the basic contributions of objects to a system briefly from a technical perspective. Let us now review them in the lightImage 263 of responsibilities. In general, the responsibilities of an object can be split into three categories:

• An object may have to know information.

• It may have to perform tasks, mostly for others.

• It may have to make decisions affecting other objects.

Many objects have responsibilities from all three categories, although the last one is perhaps less frequent: Usually an object’s decisions concern its own duties and its own internals. At the other end, objects that only holdImage 92 information are very rare and should be avoided, since operations on theImage 1.1 Image 4.1 information are more challenging to code and cannot be encapsulated.

In the example, the NewWizardRegistry holds information about available file types, but it also gathers information from the various extensions and wraps it into convenient IWizardDescriptors. The NewClassWizard PageImage 2.4 holds the information about the new class’s characteristics, allows the user to edit that information, and creates the source file. The Wizard dialog holds no “information” relevant to the user, but it decides when data must be validated and when pages are flipped. It does so on behalf of the contained wizard and its pages, which are relieved from making these decisions and can concentrate on their own contributions.

Having such categories helps in describing objects. For each object, ask yourself what it has to know, what it contributes to the system’s functionality, and in which way it is a manager making decisions of more strategic importance.

Going one step further, objects often have mostly responsibilities fromImage 261 only one category, which leads to the concept of role stereotypes: information holders, service providers, controllers, structurers, interfacers, and coordinators. Their descriptions have been given earlier.Image 1.8.1


Identify responsibilities and assign them to individual objects.


Responsibility-driven design then works from the tasks to be solved. In the end, the application has to implement some desired functionality. That functionality is often given by use cases, which describe possible interactionsImage 147 Image 47 of the users with the system: The users stimulate the system by their input and expect some observable behavior in return. As a result, the softwareImage 7.1 Image 10.1 becomes event-driven, which ties in with the view of objects responding toImage 1.4.1 messages.

Designing then means breaking the functionality down into responsibilities—first into broad, comprehensive responsibilities that we assign to the system’s modules, and then into small-grained ones describing individual steps or aspects of the overall solution. Those responsibilities get assigned to individual objects.


Invent objects to take on responsibilities where necessary.


In the beginning, there are, of course, no objects that could take on the responsibilities. During the design process, newly discovered responsibilities may not fit in with any existing object. Inventing objects is therefore a central activity in design. The basic idea is this:


If something needs doing, somebody has to do it.


Compare this to the real world: If the kitchenette in the office needs regular cleaning, then someone must be responsible for it. There must be a well-established mechanism that triggers the cleaning, because otherwise the room quickly starts looking quite messy.

Let us try our hand at the example, by designing a File Creation Wizard from scratch. This wizard must show a list of file types, as well as Next and Previous buttons, and must gather the correct extra information required for a specific file type. Fig. 11.4 shows a first attempt. The File CreationWizard manages the overall process. It uses a helper FileType List for the first step, because that is a self-contained task. Since the entry of the extra information depends on the file type, we create a role ExtraInformationPage (the italics in the figure indicate that it is a role). Finally,Image 9.1 knowing the benefits of model-view separation, we invent a role Initial ContentCreator, again with one kind of creator for each file type. The FileCreationWizard is responsible for transferring the entered extra information from the view-levelExtraInformationPage to the model-level InitialContentCreator.

Image

Figure 11.4 First Attempt at a FileCreationWizard

Of course, this design is less elaborate than Fig. 11.2, but then we did not aim for reuse of the wizard dialog. Even so, we managed to observeImage 9.2.2 model-view separation, so that the content creator can be tested on its own, without clicking through the wizard.


Image To sharpen the perception of what is specifically “object-oriented,” let us stand back for a minute. All programming paradigms, such as object-oriented, procedural, or purely functional approaches, provide some way of subdividing the implementation of functionality. The crucial difference between the paradigms arises from the shape of subtasks that single implementation units can take on. In procedural programming,Image 1.8.4 subtasks are algorithmic steps toward a solution. In purely functional programming, they are transformations of (mostly tree-shaped) values. In object-oriented programming, they are reactive entities that communicate via messages. To fully exploit objects, it is therefore best to postpone concerns about algorithmics and data structures during design: While it is necessary that the design can be implemented, and efficiently too, the focus must be on a sensible task assignment that can be understood on its own, without looking at any code.



Do not assign the same responsibility to different objects.


The metaphor of “responsibilities” has a second important aspect: If a person is responsible for a given task, then the individual fulfills that task completely and reliably, and does not share that task with other people. Likewise, an object should be made the sole authority for its responsibilities. The obvious reason for this goal is that you will not have to write code twice or garble your design by copy-and-paste programming. One level deeper, the exact details of responsibilities are likely to change. ConfiningImage 11.2.3 the changes to a single place keeps the software maintainable. As we willImage 11.5.5 see, however, this simple goal involves some rather subtle implications and challenges.


Plan for collaboration to keep the responsibilities small.


Throughout the design, components and objects will rely on collaboration to fulfill their responsibilities: An object never does things that another object can already do. While identifying individual responsibilities, we will therefore also plan for the necessary collaborations: If an object requires help in its tasks, it must have a reference to the object that can provide the help. The overall goal is to keep objects so small that their behavior can be subsumed under a single, well-defined responsibility.Image 11.2

In the running example from Fig. 11.2, the NewWizard relies on the NewWizardRegistry to interact with the Eclipse extensions mechanism.Image 12.3.3 It delegates the task of displaying all found file types to the NewWizard SelectionPage.


Image At this point, it is important to recall the object-oriented view on methods. A methodImage 1.4.1 call is not merely a technical invocation of a subroutine implementing a specific algorithm. It should instead be read as a message to which the receiver reacts appropriately, at its own discretion. Since the ultimate responsibility for the reaction lies with the receiver, it is also the receiver that decides which reaction will best suit the request.



Mechanisms structure sequences of individual collaborations.


Just as each object has few and small responsibilities, each individual collaborationImage 11.2 between objects usually concerns a tiny aspect of the overall application’s functionality. It often takes the accumulated effect of many collaborations between several objects to exhibit some desirable, externally visible behavior.

We will use the term mechanism for a sequence of logically connected collaboration steps that together achieve a common goal. Mechanisms induce a switch of perspective: Rather than asking what a particular object does and how it reacts, we ask how a particular overall reaction is achieved through collaborations among possibly several objects. In UML, mechanismsImage 47 are rendered as sequence diagrams. The comprehensive treatment there underlines the importance of the concept.

In the example, one may rightly ask: How does a wizard-like dialog ensure that the Next button is enabled only if the current page is filled in with complete and valid data? The solution is that the current page observes its input fields and invokes updateButtons() on its wizard container, which in turn calls the page method canFlipToNextPage(). But this is not yet the whole story. Many concrete pages derive from Wizard Page,Image 3.1.4 and that base class provides some infrastructure. For instance, a flag isPageComplete captures the current status and updating the flag links in with the raw mechanism:

org.eclipse.jface.wizard.WizardPage


public void setPageComplete(boolean complete) {
isPageComplete = complete;
if (isCurrentPage()) {
getContainer().updateButtons();
}
}



Image Since the chosen mechanisms determine to a large degree the complexity of the implementation, it is important to consider alternatives. In the example, one could require the pages to act as Java beans and to inform PropertyChangeListeners about data modifications.Image2.1.4 The wizard container would then simply listen to the changes. However, there would always be only a single observer, and the page would have to translate SWT events to PropertyChangeEvents. The special-purpose communication through update Buttons()certainly reduces the overall implementation effort.


Image 9.4.3Another example of a typical mechanism is seen in the task of updating the screen after a change in the model. Fig. 9.14 (on page 500) explains how the discovery of a data modification after several intermediate stepsImage 7.8 leads to a callback to paintControl(). The mechanism is designed in this complex fashion to keep it general and to enable the window system to optimize the painted region in between.


Responsibilities and collaborations must make sense locally.


Mechanisms are necessary to structure and explain sequences of collaborations. However, they incur the danger that individual collaborations cannot be understood at all without also considering the larger picture. As a result, maintenance can easily become a nightmare: Having no time to read through the design documents or having mislaid these documents a long time ago, the maintenance developer must trace through the collaborations using a debugger to reconstruct the intentions of individual method calls.

The only way to avoid this problem is to constantly switch between the global perspective of mechanisms and the local perspective of responsibilities and collaborations of individual objects. It is a good exercise to “write”Image 11.2.1 a mental documentation for individual objects from time to time: Can you still say in one or two sentences what the object or a particular method does without referring to the subsequent reactions of its collaborators? Only such localized descriptions ensure that the individual classes of the system can still be understood by the maintenance developer.

In the example, the mechanism for revalidating the data and enabling or disabling the wizard buttons can be documented locally. In the description of IWizardPage, and the base class WizardPage, one would have to (1) say that method canFlipToNextPage() must validate the data and (2) leave a remark that the object must invoke updateButtons() whenever the data has changed.

In the Graphical Editing Framework, one can still describe an individualImage 214 “edit part” (i.e., the unit of composition of drawings) without understanding entire sequences of collaborations. It is necessary to understand only the edit part’s expected reactions to specific stimuli, mostly sent as Request objects.


Image Disciples of other paradigms often bash object-oriented programming because of the necessity of combining several collaborations to achieve some overall effect. They complain that one never sees where anything particular gets done because so many objects are involved and each contributes so little. This perception has two possible sources. First, the particular design might be so bad that it is indeed impossible to understand the individual objects separately. Second, the developer in question might not be prepared to take the leap to the object-oriented way of thinking, where one focuses on individual objects and trusts subsequent collaborations to occur as would be expected. A similar case occurs for novices in functional programming, where many tasks are approached by (structural) recursion. A fundamental insight on recursion is this: Never think about a recursive function recursively. One does not understand a recursion by unfolding the calls a few levels deep. One has to focus on the contribution of the individual step, trusting the recursive calls to deliver the expected results reliably. Similarly, one understands a loop not by unrolling a few iterations, but rather by seeingImage 4.7.2 Image 144 the overall picture, as expressed in the loop invariant. In all three cases, the particular way of “seeing the point” in a solution requires some practice and experience with the particular units used for subdividing tasks.



CRC cards help to start designing.


Let us now turn to the design process itself. Design in many places is a creative, formally largely unconstrained activity. To introduce some structure, you can use CRC cards. The abbreviation stands for “class, responsibilities,Image 32 and collaborators.”

Here is how to start with CRC cards: You buy a few hundred medium-sizedImage 11.3.2 index cards and use one for each object or role that you identify. Each card gets subdivided as shown in Fig. 11.5. At the top of the card, you write the class name. A small right margin holds the collaborators, just to capture the overall graph structure from Fig. 11.2. The remaining area holds the responsibilities.

Image

Figure 11.5 Example CRC Cards

The cards in Fig. 11.5 capture the essence of the design: WizardDialog collaborates with the contained wizard and its pages, as well as with the outer Shell. It has to provide the outer frame for the actual wizard, and it initiates and decides about the flipping between pages. Also, it triggers data validation, by calling canFlipToNextPage() and isComplete() on pages. Each IWizardPage then has to create SWT widgets for data entry and must validate the data. It must dynamically provide the next and previous pages in the sequence. Finally, it has to react to changes in the entered data by asking the container for revalidation.

Since CRC cards are cheap, lightweight, and easy to manipulate, they invite you to sketch designs quickly, and to throw away superseded versions of objects without regrets. Also, the limited area on the index cards forces you to split large classes—there is simply not enough space to describe too many or too complex responsibilities.


Image CRC cards establish a useful shape for thinking about design. Experienced designers and developers will often find that CRC cards assemble themselves in their heads and that they can type out the classes, or at least their public methods, immediately.Image 32 CRC cards are an effective way of learning object-oriented design, but you must not feel constrained by the format for all times and in all situations.



Start from your idea of a solution.


Image 11.5.2One important aspect of design is that it always captures one way of thinking about the given problem and one approach to solving it. Different people will understand a challenge differently, and they will arrive at different designs. One key contribution of a good design is that it captures the team’s common understanding of the intended solution.

Before embarking on a design, you must have at least a general understanding of the overall solution strategy. Likewise, figuring out the details of the design is a good time for elaborating one’s idea of the solution.


Design, evaluate, improve.


Design is always an iterative process. There is rarely one optimal solution, and in any case, one tends to forget important details when first designing networks of objects. As a result, one has to sketch out a few possibilities before deciding on their relative merits. CRC cards are just the tool for this kind of lightweight iteration.

Here are some evaluation criteria. Most fundamentally, one has to establish that the design solves the given problem: Will all the use cases be covered by the envisaged collaborations and mechanisms? Do all objects have access to the required collaborators? Beyond that, one can evaluate the match with object-oriented principles: Are the individual objects small andImage 11.2 self-contained? Are they really reactive entities or are some objects merelyImage 92 storing information that other objects work on? Finally, there are possible strategic goals: Can the crucial components be rigorously unit-tested, byImage 5.1 placing them in a test fixture? Does the design respect model-view separation?Image 9.1 Can the implementation be extended to cover modified or additionalImage 12.3 use cases? Can parts of the implementation be reused in different contexts?Image 12.4

Having the design available in some external form, such as CRC cards, enables us to point to special aspects and match them against our idea of desirable designs as well as against the given problem. Also, any implicit assumptions not yet worked out properly will be uncovered immediately.

The goal of these evaluations is to expose possible flaws early on, while changes are still quick and cheap. If we find that a missed use case does not fit the existing design only after completing substantial parts of the implementation, the effort to integrate it by changing the code structure will be much higher. Take the opportunity to seriously challenge your own designs through CRC cards.


Become aware of the decisions you make.


Designing always involves making decisions: Which object is responsible for which aspect of some reaction? Is a responsibility small enough to be carried by a single object or should it be split and distributed among collaborators? Do we need to generalize a solution to enable reuse later on while incurring some implementation overhead now?

To become good designers, we have to become aware of which decisions we make for which reasons. If we fail to see the necessity of a decision,Image 11.5 we are likely to blunder on in one direction without even being aware of possibly smoother and shorter paths. If, in contrast, we have a bunch of alternatives right at our fingertips, we are more likely to come up with a useful design. We also gain the consolation that if we have taken the wrong turn at some point, we are not stuck but still have a number of alternative and promising paths left to explore.

11.2 The Single Responsibility Principle

Image 170 Image 172If you are looking for a single guideline that will make a design come out “truly object-oriented,” it is certainly the Single Responsibility Principle, abbreviated as SRP. Its diligent application will lead to small classes thatImage 1.1 collaborate in well-defined ways, and to software that is easy to change and to maintain.

11.2.1 The Idea

The Single Responsibility Principle states that each class should have only one responsibility and should delegate any tasks not related to that responsibility to its collaborators. For an illustration, let us go back to theImage 11.1 wizard creating new files in Eclipse. With little time and little experience, we might just start on the user interface with the WindowBuilder and addImage 7.1 the functionality to the event handlers as we go along. We will probably end up with the design in Fig. 11.6. The FileCreationWizard object displaysImage 12.3.3 a window with buttons for navigation; it looks through the registered file types; it allows the user to select a type; and finally it creates a wizard page to gather the information specific to the file type. Only that last task is delegated, and only by necessity: Because the information is different, we need different screens to collect it. Basically, this is an instance of theImage 1.3.4 STRATEGY pattern.

Image

Figure 11.6 A FileCreationWizard with Too Many Responsibilities


Make each object responsible for one thing.


Obviously, the FileCreationWizard in Fig. 11.6 will be a huge class. We have surrounded its responsibilities with dotted lines to indicate that each will require several fields and methods in the implementation.

Suppose now that we “explode” this huge class into separate classes, one for each responsibility. We arrive at the design in Fig. 11.7. Basically, weImage 11.1 have moved each thing that needs doing to a separate object that actually does it. Only the ExtraInformationPage retains two responsibilities, because in this way it can apply the entered information directly during file creation. The similarity to Eclipse’s own design in Fig. 11.2 (on page 571) is really quite remarkable: Although one could argue that we were bound to come up with the same design because we knew the original, the tasks in Fig. 11.6 really needed doing, and now we have done them.

Image

Figure 11.7 Refactored FileCreationWizard


The SRP keeps objects small and understandable.


A major benefit of applying the Single Responsibility Principle is that the code base of an object remains small: If independent tasks are distributed over several objects, then each of them has a simpler implementation. The code also becomes easier to understand, because the reader knows that any particular snippet must somehow fit in with the goal of fulfilling the object’s one responsibility. This tight logical connection between an object’sImage 12.1.3 different code pieces is also called cohesion.


The SRP keeps objects maintainable.


The new design also clarifies the later implementation in two respects. First, it is clear where to look for the implementation of a specific piece of functionality that may need fixing or changing. This helps greatly in maintenance work. Second, the dependencies between the different tasks become clearer: Within the huge object in Fig. 11.6, any piece of code may access and manipulate any field, and any method may call any other method. In contrast, in the “exploded” design of Fig. 11.7, each object can access onlyImage 1.1 the public methods of other objects; the fields remain encapsulated.


Image A deeper reason that makes the “exploded” design desirable is that the clarificationImage 4.1 of dependencies leads to smaller and less complex class invariants. The fields needed for each responsibility in Fig. 11.6 are linked by consistency constraints (i.e., by classImage 4.1 invariants). The invariant of the entire class combines these into a huge assertion. Each public method must prove that it establishes that assertion in the end. The developer must therefore understand the invariant completely to exclude any possible interactions, even if a public method touches only a very few fields.



Give each object a coherent overall purpose.


One question arises immediately: How can an object with only one responsibility ever get anything significant done? And how can a system consisting of only such minuscule objects ever fulfill its users’ demands?

The answer is that the actual power associated with a responsibility depends largely on the granularity and abstraction level chosen for expressing the responsibility. The unit “responsibility” is a conceptual measure, not a technical one. For instance, the responsibility to gather existing extensions in the designs of Fig. 11.7 and Fig. 11.2 involves a fair bit of data structures and boilerplate code, as well as some nontrivial knowledge about the Eclipse API for accessing extensions. Still, it is one self-contained responsibility that the object NewWizardRegistry fulfills.

The Single Responsibility Principle is about logical cohesion: All tasksImage 12.1.3 Image 236 that an object undertakes must be subsumed under its responsibility, but this does not mean that there is only a single task from a technical perspective. It might be better to say that an object has a single purpose, which also captures a specific reason why the object must be present in theImage 92(“Lazy Class”) system.


Learn to tell the stories of your objects.


Another helpful intuition is that of telling “stories.” When we ask, “What’s the story behind this?” we actually mean this: What is a convincing explanation? Why should I believe the author? Why should I “buy” the argument? A story is often related to a message: When a blog entry, for instance, tells a good story, we mean that it has a point and states it clearly and convincingly.

Telling “the story” of an object well means explaining what it does, why it is a useful member of the community, why the implementation effort is justified, and why its public API is exactly right. Imagine you have to sell your objects to your teammates and you have only a few minutes to convince them you did a great job. Which aspects would you emphasize?

The purpose of this exercise is to form a clear idea of each object’s goals, as well as of its limitations and the tasks it delegates to its collaborators: Someone who knows his or her own expertise is better in focusing on this area. It may take some time to find “the story” of your object, but it is always worthwhile, since it helps you to focus, to find the “simplest implementation,”Image 28 and to communicate the results within the team.


Describe an object’s purpose in a single sentence.


To check whether you have succeeded in assigning a single responsibility to an object, try to summarize its purpose in one sentence. Here are some examples from the original new wizard (Fig. 11.2):

• The NewWizardRegistry collects the declared extensions into a conveniently accessible data structure.

• The NewClassWizardPage enables the user to enter the parameters necessary for creating a new source file and then creates the file.

• An IWizardDescriptor encapsulates a declared file type to simplify setting up a wizard to handle the file type.

• A WizardDialog acts as a container for a concrete wizard with several pages.

An important point is that the one sentence must be a plain, short specimen. Once you start stringing together subclauses joined by “and,” “if,” and “but,” you defeat the purpose of the exercise. Finding a sentence meeting this criterion is actually quite a challenge. The author admits freely that he had to fiddle with the formulation of the given examples to get there.

If you cannot come up with a short sentence that captures the object’s purpose, the design may be flawed. A case in point is the NewClassWizard Page:Image 11.1 Its description does contain an “and”—and we have already found that it violates model-view separation.


Condense an object’s single responsibility into the class name.


Image 1.2.3A single-sentence purpose is nice, but it is better still to condense the purpose even more by expressing it in the class name. For example, a Wizard Dialog is a dialog containing a wizard; a NewClassCreationWizard is a wizard responsible for creating a new source file with a Java class. Similarly, knowing that a “registry” in Eclipse parlance is something that manages specific objects, we see that the NewWizardRegistry manages extensions offering “new wizards.” Renaming NewWizardNewPage to NewWizard SelectionPanelImage 7.5 would clarify that it is a composite widget offering several “new wizards” for selection.

11.2.2 The SRP and Abstraction

The Single Responsibility Principle has more benefits than just keeping the code base of individual classes small and coherent. An important aspect is that it guides developers toward creating useful abstractions.


Assigning single responsibilities means understanding the solution.


Image 11.2.1You will quickly note that it is actually very hard to come up with a single sentence that describes an object well and comprehensively. The reason is simple: You have to identify the object’s really important aspect from which all other aspects follow logically. In other words, you have to arrange your possibly many ideas about what the object does into an overall structure.

Once you succeed, however, you gain a better understanding of your object. The object is no longer a flat collection of tasks; instead, these tasks are interrelated. You know which ones are important and which ones are mere technical details; which ones are crucial and which ones are incidental; and which ones characterize your object and which ones it may share with other objects.


The SRP helps to abstract over the technical details.


The Single Responsibility Principle also makes it simpler to understand theImage 11.1 application’s overall network of objects. Because you can summarize each object in one sentence, you can skip quickly from one object to the next without cluttering your mind with the technical details of each. Once your mind is freed from these details, it can hold more of the actual design.


The SRP helps in taking strategic design decisions.


Strategic insights and decisions are possible only at the level of abstraction created by brief summaries of objects. To make decisions, you have to juggle in your mind the design as it is, and most minds are not capable of holding too many details at a time. Brief summaries, in turn, are possible only if each object takes on only a small task, a single responsibility.


The SRP is indispensable in communicating the design.


There is nothing more boring and ineffective than listening to teammates expounding on the internals of their classes at the slightest provocation. Many developers fall for the temptation of boasting about their technical exploits.

The Single Responsibility Principle enables a team to communicate effectively: Because each team member prepares a one-sentence summary of his or her objects in advance, answers become shorter. Because the answers link directly to the overall system purpose, other team members can pick them up and store them away quite easily. In the end, the whole team getsImage 28 a pretty good overview of the system, without having to understand the technical details of every class.


The SRP also applies to class hierarchies.


The first association that springs to mind in connection with abstraction is inheritance. Since responsibility-driven design focuses on objects, rather than classes, we will look only at the basics. The Single Responsibility Principle applies to arranging code into classes, so it applies to hierarchies as well. From this point of view, each subclassing step and each level withinImage 11.4 the hierarchy has its own purpose and its own responsibilities. For an extended example, you can look at the hierarchy of editors in Eclipse andImage 12.2.1.2 in particular the chain EditorPart,AbstractTextEditor, StatusText Editor, and AbstractDecoratedTextEditor.

11.2.3 The SRP and Changeability

It has been noted from the infancy of computer science that modularization is necessary because software probably needs to be modified during its lifetime. In his seminal paper, Parnas postulates: “The essence of informationImage 205 Image 11.5.1 hiding is to hide design decisions that are likely to change, and to make modules communicate through interfaces.” It must be said that—given Parnas’s words were written in 1972—“design” means mostly design of data structures and algorithms. Still, the point is there: We make decisions now that we are pretty sure we will have to revise later on. How can we do this without breaking our product?


The SRP helps in restricting the impact of necessary changes.


Image 172,170 In his presentation of the Single Responsibility Principle, Martin defines the term responsibility itself as “one reason to change.” If each object has a single responsibility, then there is one kind of change that the object will absorb, one kind of change from which it insulates the rest of the system. The unit of task assignment coincides with the unit of changeability.

Image 205 It is interesting to note that Parnas, in 1972, also made this connection: “In this context ‘module’ is considered to be a responsibility assignment rather than a subprogram.” It seems that the metaphor of “responsibilities” is, indeed, very appropriate for reasoning about good software organization.

The Single Responsibility Principle does not immediately guarantee that changes will not ripple through the system. This outcome can be achievedImage 11.5.1 Image 12.2 only by striving for information hiding and designing for flexibility throughout. However, the Single Responsibility Principle lays the foundation: When an object acquires a responsibility, it can and should become zealous about it and should not allow other objects to meddle with its own implementation decisions. Guarding these decisions as a private secret is the right step to take.


The SRP helps to locate the point of change.


At the same time, the Single Responsibility Principle helps one find theImage 11.2.1 place that needs changing. Assigning a responsibility to an object also means that no other object will take on the same responsibility. If some behavior needs changing, we can quickly find the class that contains the code, through its one-sentence summary of its purpose.

Once we start looking into a class, we are sure that all the code we see is potentially affected by the change, simply because the class takes on only a single, coherent responsibility.


The challenge in design is to anticipate the probable changes.


How then, do we assign responsibilities? So far, we have proceeded largely by intuition. Now, we get a new criterion: Each responsibility should enclose one area where the software is likely to change. Anticipating such changes is far from simple. While we strive to find a solution for a given set of requirements, we have to think at the same time about similar solutions for similar requirements. A few heuristics will help to identify the critical points in the design.

• Experience in building a number of similar systems gives us a pretty good idea of the variability between different instances.Image 70

• The insecurity we might feel in formulating and solving a specific problem can be a hint that we might have to revisit the code later on.

• If we have made a deliberate and well-informed decision between several alternatives, as professionals, we must be well aware that the others may come in handy later on.

• We are bringing in a library with which we have little prior experience. To make it simple to switch to a competitor, we hide all accesses in objects.

• Sometimes we deliberately build a suboptimal solution, perhaps even a mock object as a temporary stand-in for the the real and complexImage 5.3.2.1 implementation.

From a language perspective, these guidelines are all about encapsulationImage 1.1 Image 11.5.1 of private elements behind the public facade of an object. Specifically, the remainder of the system can keep on using the same external interface as long as the internal implementation fulfills the established contracts.Image 4.1


The two notions of responsibility coincide naturally.


Fortunately, there is also a direct link between the intuitive approach of chopping up given requirements into individual responsibilities and the search for responsibilities as spots of likely change: Changes to a software are usually triggered by changes in the requirements. If these requirements vary only slightly, then chances are that they will fall under the same one-sentence summary of the object currently handling the requirement. The change then concerns only the internals of that object, and these internals are encapsulated within the object through its public interface. As a result, the remainder of the system is not affected, and the impact of the changing requirements is absorbed gracefully by the existing software structure.

11.3 Exploring Objects and Responsibilities

Having mastered the concepts and basics of responsibility-driven design as well as the all-important Single Responsibility Principle, we will now try out this approach on a self-contained project: a simple function plotter.

11.3.1 Example: A Function Plotter

Fig. 11.8 shows the overall application we will implement. In the lower-left corner, the user enters a formula, which then gets plotted in the main area above. In the lower-right part, the user selects the region of coordinates and the type of axis (simple or grid).

Image

Figure 11.8 The Function Plotter Example

The whole thing seems simple enough, and you have probably implemented something like it just for fun in an hour or so. The challenge and goal here is not so much the functionality, but the design: How can we reuse the shift-reduce parser from the INTERPRETER pattern and the MiniXcelImage 2.3.3 Image 9.4 application? How can we keep individual aspects such as the certainly suboptimal choice of the plot region changeable? How can we build the application from small individual objects that collaborate only in well-defined ways? How do we shape those collaborations so that the objects remain as independent from one another as possible?Image 12.1

We will see that to answer these questions satisfactorily, we have to tie together many individual design principles, design patterns, and elements of language usage that we have so far treated separately. Seeing them connected in a comprehensive example gives us the opportunity to explore their interconnections and synergies. At the same time, we will introduce more details about the known concepts.

The subsequent presentation focuses on the concrete code of the function plotter and justifies the design decisions from the resulting code structure. Afterward, we will give a brief summary of the development. To keepImage 11.3.4 the wider view, you may wish to peek ahead at Fig. 11.11 on page 617, which depicts the main objects introduced and highlights their principal relationships.

11.3.2 CRC Cards

Before we can start, we need a better handle on design. The box-and-line drawings used in the presentation so far are all very well, but they hardly constitute a clean format for larger designs. At the other end of the spectrum, a semi-formal language like UML can easily stifle our insightsImage47,198 and creative impulses.


Class, responsibilities, collaborators—CRC.


The insight that objects are characterized by their responsibilities and their collaborations has led to the proposal of writing down the class, the responsibilities,Image 32 and the collaborations onto small colorful index cards—in short, CRC cards. We have seen the key idea of CRC cards already. In particular,Image 11.1 Fig. 11.5 (on page 584) gives two simple examples. Now, we will add a few remarks on how to use them.


Use CRC cards to lay out candidate designs quickly.


Writing class names and responsibilities onto index cards may not seem such a great breakthrough at first. However, it turns out that the medium of CRC cards helps to push the design process in the right direction.

• Since CRC cards are small, there is no space to write up many or complex responsibilities. The medium enforces a sensible subdivision and drives us toward the Single Responsibility Principle.Image 11.2

• CRC cards are cheap and easy to manipulate. This encourages you to write up and explore alternative designs, and to discard those that prove wrong on closer inspection.

• CRC cards appeal to our visual and tactile understanding of situations. You can arrange tightly linked objects near each other, and stack a class and its helpers, or a role and its concrete realizations.Image 11.1 You might also want to choose different colors for roles and concreteImage 1.8.7 objects, for different types of objects such as boundary objects, or forImage 9.1 the model-level and view-level objects.

• CRC cards can be moved around freely. This enables you to explore collaborations—for instance, by picking up one object and moving it temporarily near to its collaborator while describing a message, and then back to its original place in the design. You can also explore various configurations and layouts of the cards on a table and see which ones best reflect your intuition about the relationships between the objects.

• CRC cards are fun and lightweight. If design is a creative process, then its impetus must not be smothered under heavyweight design tools with strict usage guidelines and thick manuals. With CRC cards, you can just start designing the minute you have an idea or a problem.

Image 11.1In some sense, CRC cards play a role similar to that of metaphors in software engineering: Because software objects are invisible, intangible, and therefore largely abstract entities, it is good to link them to the concrete reality, with which most of us have extensive experience.

11.3.3 Identifying Objects and Their Responsibilities

Finding objects is not a simple task in general. It is not sufficient to look at the user interface or to model mere data structures from the applicationImage 11.1 domain. We have to come up with a whole collection of objects that togetherImage 263 set up a software machinery to fulfill the users’ expectations. This also means that the ultimate source of all responsibilities of the system’sImage 11.1 objects is the set of use cases and the specified reactions: If something needs doing, somebody must do it. But the reverse is also true: If somebody does something, then the effort should better be justified by some concrete reaction that the system must implement.

Experienced designers carry with them a comprehensive catalog of their previous designs and model their new objects based on successful precursors. They know which kinds of tasks objects are likely to perform well and which objects are likely to be useful.

We will therefore provide some common justifications for why particular objects are present in a system and which kinds of purpose they fulfill. Part IImage 1.8 has given a similar overview from a technical perspective, and it may be useful to revisit it briefly. Now, we will reexamine the question from the perspective of responsibilities, rather than class structure.

The presentation here proceeds from conceptual simplicity to conceptual abstraction—from the objects that are obviously necessary and easy to recognize to the objects that emerge from some previous analysis. We deliberately refrain from giving an overall picture up front, as we did forImage11.1 the introductory example. Instead, we follow the reasoning steps as they could take place in the design process. In many steps, we make explicit the connections to the general structuring principles introduced earlier on. We hope that this arrangement will help you to apply similar reasoning within your own projects. A summary of the development is given in Section 11.3.4.

11.3.3.1 Modeling the Application Domain

Any successful design will finally lead to software that fulfills all given requirements. For instance, one can start with use cases to trace the system’sImage 11.1 Image 11.3.2 reaction through the object collaborations. Alternatively, one may start from the central business logic and the core algorithmic challenges. In any case, the first objects that spring to mind emerge from the analysis of the application domain—they are domain objects.


Represent concepts from the application domain as objects.


In the example, when we think about “plotting a function graph” as shown in Fig. 11.8, we will at some point talk about a “plot region” as the ranges in the x- and y-coordinates that are displayed on the screen. Translating this concept into an object yields a simple bean that keeps the minimal andImage 1.3.3 maximal coordinates:

fungraph.PlotRegion


public class PlotRegion {
private double x0;
private double x1;
private double y0;
private double y1;
. . . constructor, getters, and setters
}


Another example is the function that gets plotted. Ideally, we would like to plot functions f :ImageImage ∪ {⊥}, functions from the reals to the reals where some values are undefined (value ⊥). Of course, we use double to represent Image. But beyond that, what is “the best” representation for the function itself? A syntax tree? Some kind of stack machine code? Furthermore, doImage 2.3.4 we have to implement the formula representation ourselves or will we find a full-grown, reusable library? To defer such decisions, we introduce a role Plottable Function: We simply specify how the function will behave without deciding right now on its concrete class. The method funValue() in the following interface captures the function f, with an exception signaling ⊥. Adding a self-description method is always useful for displaying information to the user.

computation.PlottableFunction


public interface PlottableFunction {
String getDescription();
double funValue(double x) throws Undefined;
}



Image One question is whether an explicit getDescription() method is useful, given that every object in Java has a toString() method that can be overridden. According to its API specification, that method is supposed to return a “a concise but informative representation that is easy for a person to read.” Certainly, that statement is ratherImage 44(Item 10) vague and open to interpretation. The general advice is to include all information useful for the human reader, if this is possible without returning unduly long strings. Then the object can be printed usingSystem.out.println(), String.format(), and so on, and itImage 1.8.4 will also display nicely in debuggers. For simple value objects, such as numbers, it mayImage 44(Item 10) even be possible to provide a parsing function to recreate an equivalent object from the string representation. We would argue that toString() should be geared toward tracing and debugging scenarios: The end user is not concerned with objects, so a “representation of an object” is not useful in this situation. Instead, user interfaces require specializedImage 9.1 external representations, so it is better to keep the two forms of “readable representation” separate in the code.



Starting with the application domain often means starting with the model.


Image 9.1In the context of model-view separation, we have advocated starting the application development with the functional core—that is, with its model. By focusing on the application domain, you will likely start with the business logic and therefore the model.


The application domain is a dangerous source of inspiration.


Image 92The PlotRegion object defined earlier is “lazy”: It does not do anything, but merely holds some data. Such objects often result from a superficial analysis of the application domain, where we tend to describe things and concepts statically, by capturing their relevant attributes. Many things we find in the real world are not active themselves, but are manipulated by others. Think of bank accounts in a finance application, of hotel rooms in a booking system, or of messages in an email client. They all have perfectly clear relevance for the application, yet they are passive. As a result, theyImage 1.1 do not fit in with the view of objects as small and active entities.


Think about the software mechanisms throughout.


However, we can redeem the use of domain objects by simply asking: If theImage 11.1 objects do exist in the system, which part can they play in the mechanisms of the software machinery?

In the case of the PlotRegion, a simple responsibility would be to keep interested objects informed about changes. In Fig. 11.8, both the central function graph and the plot region selector at the bottom must synchronize on the current plot region. Model-view separation, or more specifically theImage 9.2.1 MVC pattern, tells us that in such cases it is best not to assume one flowImage 9.2 of information, but rather to use a general OBSERVER pattern. For now,Image 2.1 the user can only select the region in the special widget, but perhaps a later extension will allow the user to zoom in and out of the function graph by mouse gestures on the main display. The implementation is, of course, straightforward:

fungraph.PlotRegion


public class PlotRegion {
. . .
private ListenerList listeners = new ListenerList();
. . .
public void setX0(double x0) {
this.x0 = x0;
fireChange();
}
. . . fire, addListener, removeListener according to pattern
}


As a detail, we decide to use the simpler pull variant of the pattern,Image 2.1.3 because a change in the region will require a full repaint of the function graph anyway, since incremental updates would be too complex.Image 9.4.3

fungraph.PlotRegionListener


public interface PlotRegionListener extends EventListener {
void plotRegionChanged(PlotRegion r);
}



Software is rarely ever a model of reality.


The example shows clearly that the software machinery is quite distinctImage 263 from any mechanisms found in the real world. Indeed, the reasons for introducing OBSERVER are strictly intrinsic to the software world and derive from the established structuring principles of that world. It would therefore be naive to expect that a close enough inspection of the application domain would yield a useful software structure.

The only exceptions to the rule are simulations: If the application-domain objects do have a behavior that needs to be recreated faithfully in software, chances are that one can obtain a one-to-one match.


Application objects are good choices for linking to use cases.


Even if pure application objects are rare, they do have their merits when they fit naturally into the software machinery. For one thing, they enhanceImage 24 the traceability of the use cases and requirements to the actual software. Once one has understood the use cases, one can also understand the software. If customers are technically minded, for instance in subcontracting parts of larger systems, they might care to look at the code themselves.

Image 11.2.3Furthermore, application objects may accommodate changes gracefully. If an application object reflects and encapsulates responsibilities derived from the application domain, then minor changes in the requirements are likely to be confined to the application object.

11.3.3.2 Boundary Objects

Image 1.8.7Another easily recognized source of objects is the system’s boundary, whichImage 1.8.1 contains objects that are interfacers in the terminology of role stereotypes. Boundary objects hold a number of attractions:

• They often link to concrete external entities such as the application’s file format, some XML transfer data, and so on. These entities may be available for inspection beforehand, so they can guide us in designing the software to process them.

• They are motivating because their reaction is visible externally and we may even demonstrate them to future users.

Image 11.1The design process can start from the use cases. Since we encounter the boundary objects first, we have a clear picture of their reactions.

• They come with several predefined responsibilities. For instance, whenImage 4.6 they accept data from the user or other systems, they have to shieldImage 1.5.2 the system from possible failures, inconsistencies, and attacks.

• Their interface is often determined by libraries and frameworks. For instance, when using a SAX parser for XML files, we have to provide a ContentHandler to process the result. When writing a graphical user interface, we use SWT’s widgets. When writing a web application, weImage 201 may use the servlet API.

Once the boundary objects are identified, we can start assigning responsibilities to them.


Apply model-view separation to boundary objects.


An essential guideline for designing boundary objects is to use model-view separation, even if the boundary is not a graphical user interface. In particular, the boundary objects should be as small as possible. They should not contain any business logic, but only invoke the business logic implementedImage 9.2.2 in a model subsystem. The benefits of model-view separation thenImage 201 carry over to different kinds of interfaces. Think of web services. Over time, many concrete protocols have evolved. If you keep the business logic self-contained, it becomes simpler to create yet another access path through a different protocol.


Make compound widgets take on self-contained responsibilities.


In the present example, the only boundary is the user interface. Since this is a common case, we explore it a bit further. As a first point, it is useful to create compound widgets that take on self-contained responsibilities.Image 7.5 In the example, the user has to enter the function to be plotted in the lower-left region of the screen (Fig. 11.8). We invent an object Formula Field for the purpose.

dataentry.FormulaField


public class FormulaField extends Composite {
private Text entry;
. . .
}


Once that object is in place, it can take care of more responsibilities. The formula string entered by the user needs to be parsed before the applicationImage 2 Image 2.3.3 can do anything useful with it. And if something needs doing, somebody hasImage 11.1 to do it—so why not the FormulaField? This choice is, in fact, supported by a second argument. Parsing includes validation, since a formula with a syntax error cannot be handled by the plotter at all. The general principleImage 1.8.7 Image 1.5.2 is that such validations take place in the system boundary. By having the FormulaField parse immediately and report any errors back to the user, the remainder of the system is shielded from invalid inputs altogether and can deal with clean abstract syntax trees.Image 2.3.3

The FormulaField therefore includes a widget for error reporting and a field for the parsed result. (We will examine the aspect of reusing the previous parser implementation later on.) Whenever the user presses “enter”Image 12.4.3 in the text field, the attached listener calls the methodreparseFormula().Image 7.1 If there is no parse error, the new formula is stored and reported to any interested listeners (lines 10–12). Otherwise, the error message is displayed and a null object indicating an invalid formula is reported (lines 14–16).Image 1.8.9 For model-view separation, note how the widget’s listener orchestrates the application of the relatively complex, model-level parser.

dataentry.FormulaField


1 private Label error;
2 private Expr formula;
3 . . .
4 public Expr getFormula() {
5 return formula;
6 }
7 private void reparseFormula() {
8 Parser<Expr> p = new Parser<Expr>(new SimpleNodeFactory());
9 try {
10 formula = p.parse(entry.getText());
11 error.setText("");
12 fireChange(formula);
13 } catch (ParseError exc) {
14 error.setText(exc.getMessage());
15 formula = null;
16 fireChange(null);
17 }
18 }



Image Image 2.1.4You may rightly ask whether it is really sensible to implement OBSERVER here. In the actual application, there is only a single listener, and that object could have been made a direct collaborator of the FormulaField. We have used the more general structure for three reasons. First, it enables us to explain the FormulaField in a self-containedImage 11.2.1 fashion, without forward references. This simple story in itself is an indication that our object has a clear purpose. Second, the FormulaField yields an example ofImage 12.1 Image 12.4 the rather elusive concepts of loose coupling and reusability. Third, during the actual development of the example code, we wished to finish the “obvious” part beforehand, soImage 12.2 that we actually did not know the later collaborator. Using Observer was one way of postponing this decision.



Introduce compound widgets for elements that are likely to change.


Another example of an obvious compound widget is the task of selecting the plot region: It has to be done somewhere, but there is as yet no object to do it. The naive solution is to just place a few sliders into the main window, according to the screenshot in Fig. 11.8, and somehow wire them up with the plot region.

The designed solution is to make a new object responsible for enabling the user to manipulate the plot region. The RegionSelector offers four sliders, two for the position of the graph and two for the scaling. The region is then updated whenever the sliders change. The nice thing is that now all the code concerned with the current region is confined in a single object, rather than being spread throughout the main window.

dataentry.RegionSelector


public class RegionSelector extends Composite {
private Scale xPos, yPos, xScale, yScale;
private PlotRegion region;
. . .
}


A second argument for introducing the extra object is that the selection mechanism is likely to change, because the current one has admittedly aImage 11.2.3 rather poor usability. By making one object, and only one object, responsible for the user interface of the selection, we can substitute the interface once we get a better idea of what users really like to see here.

Merely pushing the four sliders into a surrounding widget is insufficient, however. The crucial point that insulates the remainder of the system from possible changes here is the lean and clear API that does not depend onImage 11.3.3.1 the widget’s internal details: The PlotRegion object was introduced and defined long before we even thought about the necessity of manipulating the region!


Image The idea of simply setting the PlotRegion to communicate the changes made by the user is a typical instance of a mechanism built into the software machinery. TheImage 11.1 overall goal is, of course, that the graph is repainted if the user changes the region. This is accomplished indirectly by making the FunGraph widget, to be described later, observeImage 11.3.3.3 the PlotRegion object. The mechanism is, in fact, not very innovative, but is modeled after that of the MODEL-VIEW-CONTROLLER pattern.Image 9.2.1


11.3.3.3 Objects by Technical Necessity

Another set of objects that enter the design immediately are thrust upon you by the technical API of the frameworks and libraries that you are using. For instance, all elements of the user interface must be widgets, derived from the base class specified by the framework; when working with threads, you have to provide a Runnable that gets executed concurrently.Image 8 In all such cases, the object must exist for technical reasons, but design is still necessary to decide which responsibilities the object will take on beyond the minimal API specified by the framework.

In the running example, the plotter must use custom painting to actuallyImage 7.8 display the graph of the function. It registers a PaintListener on itself,Image 2.1.3 which calls paintComponent().

fungraph.FunGraph


public class FunGraph extends Canvas {
. . .
protected void paintComponent(PaintEvent e) {
. . .
}
. . .
}



When frameworks enforce specific objects, think about suitable responsibilities.


However, the implementation of paintComponent() depends on design decisions: Does the FunGraph itself draw the axis? Does it encapsulate theImage 11.3.3.7 Image 1.8.6 potential complexity of tracing out the graph in a separate object? The answers are seen in the code: The painting of the axis is delegated, but theImage 11.3.3.7 actual graph is drawn directly.

fungraph.FunGraph


protected void paintComponent(PaintEvent e) {
. . .
if (axisPainter != null)
axisPainter.paintAxis(this, g, r, region);
. . .
paintFunction(g, r);
. . .
}


Painting the graph then involves tracing out functions Image Image ∪ {⊥},Image 11.3.3.1 given by the interface PlottableFunction introduced earlier. We use a naive approach of walking from pixel to pixel on the x-axis, evaluating the function at each point, and connecting the points we find by (vertical)Image 11.3.3.4 lines. The object Scaler, to be discussed in a minute, is a helper to convert between screen coordinates and real coordinates.

fungraph.FunGraph


private PlottableFunction fun;
. . .
private void paintFunction(GC g, Rectangle r) {
Scaler scaler = new Scaler(region, r);
int prevY;
. . .
for (int x = r.x; x != r.x + r.width; x++) {
. . .
double xp = scaler.toPlotX(x);
double yp = fun.funValue(xp);
int y = scaler.toScreenY(yp);
. . .
g.drawLine(x - 1, prevY, x, y);
prevY = y;
. . .
}
}



Image Image 3.2.2In this context, PlottableFunction is a client-specific interface. Anything that can evaluate and describe itself can be plotted by FunGraph.


11.3.3.4 Delegating Subtasks

So far, we have dealt with objects that were indicated by external circumstances: concepts from the application domain, parts of the user interface, and the API of employed frameworks. Now, we turn to the first objects that emerge from an analysis of the concrete problem at hand: helpers. SuchImage 1.8.2 Image 1.8.5 objects are usually service providers; in the best case they are reusable.


If some task admits a self-contained description, introduce an object.


Here, we see a prototypical application of the general principle: If something needs doing, somebody has to do it. Moreover, if you are able to describeImage 11.2.1 a task in a single sentence, then this is a strong indication that a separate object, with a single responsibility, should take on the task.

In the running example, we have to convert between pixel-based coordinates on the screen and double coordinates for evaluating the given function. Of course, the FunGraph could do this itself. But then, the task is a snug little piece of functionality that might as well be given to a separate object. Here is our code (leaving out the analogous case for the y-coordinates):

fungraph.Scaler


class Scaler {
private PlotRegion plot;
private Rectangle screen;
public Scaler(PlotRegion plot, Rectangle screen) {
this.plot = plot;
this.screen = screen;
}
public double toPlotX(int x) {
return (double) (x - screen.x) / screen.width *
plot.getXRegion() + plot.getX0();
}
. . .
public int toScreenX(double x) {
return (int) ((x - plot.getX0()) / plot.getXRegion() *
screen.width + screen.x);
}
. . .
}



Service providers are often passive.


The Scaler uses a PlotRegion object that is to be mapped to a rectangularImage 11.3.3.1 screen area. Since a PlotRegion may change, the question is whether the Scaler should observe the changes. If this were so, then the Scaler itself would have to become observable to send messages when the result of scaling has changed. This would, in fact, make the Scaler an active, and therefore “better,” object.

Although this idea is viable, it does not fit the Scaler’s purpose: The Scaler performs a computation, and that computation is stateless. The OBSERVER pattern, in contrast, is all about notifying interested objectsImage 2.1 about state changes.

Service providers, which act on behalf of a caller, are often passive. The collaboration can then be defined very precisely based on classical contracts. If the service providers do contain state (apart from caches, whichImage 4.1 Image 1.3.6 are an internal technical detail not visible to clients), they would offer the OBSERVER pattern, which still keeps them independent of any concreteImage 12.1 collaborators.

Passive service providers are found in great numbers in the Java library. InputStreamReaders, Connections to relational databases, SimpleDate Formats, a Pattern representing a regular expression—they all wait for their callers to ask them to perform their tasks.


Active collaborators fit well with responsibility-driven design.


It would be we wrong, however, to assume that most objects taking onImage 11.3.3.6 a well-defined subtask are passive. For instance, the ApplicationWindowImage 11.3.3.2 tying together the overall interface from Fig. 11.8 delegates the entry of the formula to a FormulaField and the plot region to a RegionSelector, as we have seen. These objects are very much active, as they react to userImage 1.8.7 input and pass on any information received in this way, after validating and preprocessing it to fit the application’s internal requirements.


Factoring out tasks opens the potential for reuse.


In all cases, the fact that a task is taken on by a self-contained object opensImage 12.4 up the potential for reusing the implemented solution to the task. Had we integrated the FormulaField and PlotRegion into the Application Window and FunGraph objects, respectively, they could not have been extracted.Image 1.8.5 From a technical perspective, having a functionality available in aImage 12.4.1 Image 12.4.3 self-contained object is a prerequisite for reusing it. However, this alone is insufficient—objects are usually not reusable per se.


Factoring out tasks provides for potential changeability.


Image 11.2.3Another benefit from moving subtasks into separate objects is that the current implementation can potentially be changed. First, if the new collaboratorsImage 1.1 Image 11.5.1 take encapsulation seriously, then their internals can be exchanged at any time. For instance, the usability of theRegionSelector can be enhanced in any desirable way as long as the outcome of the selection is stored in the target PlotRegion.

One step beyond, if it turns out that entirely different implementations should coexist simultaneously so that the user can choose among them atImage 11.3.3.7 Image 1.3.4 Image 3.2.1 runtime, one can introduce a new interface and let different objects implement that interface. As with potential reuse, the existence of the helperImage 12.3 object is insufficient: Other objects must be able to reimplement the interface in a sensible manner, and the interface must be designed to be reimplemented.

11.3.3.5 Linking to Libraries

A lot of things that need doing in the software world will actually have been done already: by a teammate, by a different team in your company, by some contributor from the open-source community, or by a special-purpose company selling special-purpose components. Such ready-made solutions are then offered as libraries or frameworks. In the following presentation, weImage 7.3.2 will talk about libraries, but note that the arguments apply to frameworks as well.


Use libraries to boost your progress.


Responsibility-driven design is based on the principle that anything that needs doing must be done by some object. But this does not necessarily mean that one has to write the object from scratch. In many cases, it is better to look for a library containing a suitable object: You get the functionality almost for free. All you have to do is read a bit of documentation, look at a few tutorials, and there you are. Furthermore, a widely used library is also well tested. Any implementation you come up with will usually be more buggy in the beginning. And finally, the design of the library itself captures knowledge about its application domain. If it is a successful library, then its developer will have put a lot of thought into a suitable and effective API, efficient data structures, problematic corner cases, and so on. Before you could develop anything approaching its utility, you would have to learn as much about the application domain. We hope that these arguments will help you overcome the not-invented-here syndrome.


Place the library objects into your design explicitly.


But how does using a library relate to design? The library is, after all, finished. However, using a library should be an explicit decision of the development team. The team recognizes that a library offers an object that does something obviously useful. They would then write a CRC card for a library object and place it in into their current design. This step fixes one place in the design, and the remainder of the objects must be groupedImage 11.3.3.3 around this fixed point. In fact, since applications usually use several different libraries, the situation can become more as illustrated in Fig. 11.9: The library objects are beacons that are linked by application objects to create a new overall functionality.

Image

Figure 11.9 Library Objects in the Design

In the example, we recognize that we need to parse the formula that the user has entered. This responsibility was given to FormulaField widgetImage 11.3.3.2 earlier on. However, this does not mean that the developer of the widgetImage 2.3.3 has to write a parser. In fact, we already have a suitable parser from earlier examples. The parser also comes with syntax trees Expr for simple arithmetic expressions, so that the FormulaField can deliver Expr objects.

Now, we come to the point of linking the library into the existingImage 11.3.3.3 Image 11.3.3.1 design. Our FunGraph takes PlottableFunctions as input, so we willImage 2.4.1 have to create an ADAPTER to make the objects collaborate. It takes an Expr and implements the interface’sfunValue() method by evaluating the expression.

computation.ExprAdapter


public class ExprAdapter implements PlottableFunction {
private final Expr exp;
public ExprAdapter(Expr exp) {
this.exp = exp;
}
public double funValue(double x) throws Undefined {
Valuation v = new Valuation();
v.set(new VarName("x"), x);
try {
double res = exp.eval(v);
if (Double.isInfinite(res) || Double.isNaN(res))
throw new Undefined("not a number");
return res;
} catch (EvalError exc) {
throw new Undefined(exc.getMessage());
}
}
. . .
}



Avoid letting the library dominate your design.


Image 172(Ch.8) When using a library, there is always the danger that the fixed objects it introduces into the design will dominate the subsequent design decisions. For instance, suppose you write an editor for manipulating XML documentsImage 215 graphically. You use a particular DOM implementation for I/O. If you are not very careful, you will end up with a design in which many application objects access the raw DOM objects representing the single XML nodes, simply because they are there and come in handy. This has several disadvantages:

• You cannot replace the library if it turns out to be buggy, misses important features, or is simply discontinued.

• The DOM objects cannot take on new responsibilities from your application, so that they cannot actively contribute to solving use cases. This restricts your opportunities of useful design.

Image 92(“Bad Smells”)Beyond that, your own objects can also become ill designed. They work on someone else’s data in a procedural fashion because they cannot delegate the processing to the library objects.

In the example, we have done the right thing, but for quite a differentImage 11.3.3.1 reason: We have introduced a PlottableFunction because we were not sure what a suitable implementation would be. Now it turns out that this extra work pays off: If we find a better parser or library of arithmetic expressions, we simply have to adapt the FormulaField as the producer, the Expr Adapter as the consumer, and the code that hands the Expr objects betweenImage 11.3.3.6 them. But the central and most complex object, FunGraph, remains untouched.


Image The question arises of whether the FormulaField should provide PlottableFunctions directly, by wrapping them into ExprAdapters immediately after parsing. Then all changes would be confined to FormulaField and its helper ExprAdapter. Doing so would, however, tie the FormulaField to the context of plotting functions. It would not be usable if we wished to display, for instance, a tree view of the formula structure. In the end, there is a decision to be made; the important point is to recognize the decision and makeImage 11.1 it consciously.


The problematic dependency on libraries illustrated here will later leadImage 11.5.6 Image 170 to the more general Dependency Inversion Principle (DIP).


Avoid cluttering your design with adapters.


Of course, there is a downside to the strategy of keeping your software independent from specific libraries: The ADAPTERs you introduce can easilyImage 2.4.1 clutter your design. You should always be aware that adapters do not contribute any functionality of their own, but merely stitch together different parts of the software. Just as any class must justify its existence by some concrete contribution, so must adapters.

In many cases, the library you choose will be essentially the only one or the best one for a given application domain. It is then not justified to introduce the extra complexity of adapters—you will not switch the library anyway. Just having an adapter because it leads to “decoupling” is usuallyImage 12.2 a bad idea; it is just another example of “speculative generality.”Image 92

If you are using the best or the standard library, the opposite strategy will be more useful: Since the library’s objects reflect a thorough design of its application domain, they can act as guides or anchors in your own design. By placing them into the network of objects early on (Fig. 11.9 on page 607), you are likely to fix important decisions in the right way.


Avoid dragging unnecessarily many libraries into a project.


A second snag in using libraries occurs in larger projects. Developers usually have their favorite libraries for particular application areas. For processing XML, for instance, there are many good libraries available. Also, there are always “cute” solutions to common problems that someone or other has used before. For example, there are compile-time Java extensions for generating getters and setters automatically for fields with special annotations.

With the number of available libraries constantly growing, there is the danger of stalling progress by relying on too many libraries. In such a case, all developers on the team will have to learn all APIs. You will have to keep track of more dependencies and check out updates. And if the developer who introduced his or her pet library into the project leaves, serious trouble can arise.

It is better to aim for consistency and minimality: Using the same solution for the same problem throughout a project’s code base is always a good idea. Apply this principle to libraries as well. Also, for the sake of smoothing the learning curve, it may be better to spell out simple things like generating getters and setters instead of using yet another tool. If another library or tool seems necessary, be conservative, prefer standard solutions to “super-cool” ones, and find a consensus within the team.

11.3.3.6 Tying Things Up

So far, we have introduced objects that distribute among them the work arising in the given use cases. Once the distribution is complete, it is usually necessary to tie together the different parts into a complete whole. SuchImage 1.8.1 objects often fall under the role stereotype of structurers.


Create objects that are responsible only for organizing the work of others.


In the example, the ApplicationWindow creates the overall appearance of Fig. 11.8 by creating and linking the introduced compound widgets. Note how the RegionSelector and FunGraph share the PlotRegion toImage 9.2.1 propagate the user’s selection, following the MODEL-VIEW-CONTROLLER pattern.

main.ApplicationWindow


public class ApplicationWindow {
. . .
protected void createContents() {
shell = new Shell();
. . .
PlotRegion r = new PlotRegion(-10, 10, -10, 10);
. . .
FunGraph funGraph = new FunGraph(shell);
funGraph.setPlotRegion(r);
. . .
RegionSelector sel = new RegionSelector(entry);
sel.setPlotRegion(r);
. . .
}
}


Image 2.2.1The object ApplicationWindow is the owner of the different parts and links them as it sees fit. Quite a different kind of “tying together” of parts is seen when an object sits in the middle of several objects and links them by actively forwarding messages or triggering behavior as a reaction to messages received. Such an object is a MEDIATOR: It encapsulates the logicImage 7.7 for connecting other objects.

In the example, we introduce a Mediator object for demonstration purposes. It receives notifications when a new expression has been entered and when a new type of axis has been chosen, in the lower-right corner of Fig. 11.8. (We treat the handling of different axis types later in this section.)Image 11.3.3.7 In both cases, the mediator passes the new choices to the FunGraph, which will update the display.

main.ApplicationWindow.createContents


Mediator mediator = new Mediator();
. . .
mediator.setFunGraph(funGraph);
. . .
form.addExprListener(mediator);
. . .
ComboViewer chooseAxis = new ComboViewer(entry);
. . .
chooseAxis.addSelectionChangedListener(mediator);
. . .


The mediator is not typical, in that its reactions are somewhat simplisticImage 100 and information flows in only one direction, toward the FunGraph. Nevertheless, someone needs to organize this flow, so we have introduced the new Mediator object. Also, the forwarding is not completely trivial, but requires some adaptations: A newly selected axis must be extractedImage 11.3.3.7 from the selection event, and the raw Expr parsed by the FormulaField must be wrapped to obtain a PlottableFunction.Image 2.4

main.Mediator


public class Mediator implements ExprListener,
ISelectionChangedListener {
private FunGraph funGraph;
public void setFunGraph(FunGraph funGraph) {
this.funGraph = funGraph;
}
public void selectionChanged(SelectionChangedEvent e) {
AxisPainter c = (AxisPainter)
((IStructuredSelection) e.getSelection())
.getFirstElement();
funGraph.setAxisPainter(c);
}
public void exprChanged(
final ExprEvent e) {final Expr exp = e.getExpr();
if (exp != null)
funGraph.setFunction(new ExprAdapter(exp));
else
funGraph.setFunction(null);
}
}



Image You will have noticed a slight asymmetry in the design: The FunGraph observes the PlotRegion directly, but depends on the Mediator to supply the function and type of axis. Why does it not observe the FormulaField and the ComboViewer? Or conversely, why does the Mediator not also forward the PlotRegion to the FunGraph? The argumentImage 12.1 against the first modification is that the FunGraph becomes more closely coupled to its environment and is less reusable: While it does need a PlottableFunction and anAxisPainter, it does not care where they come from. It accepts them passively, asImage 1.3.4 parameters to its behavior. The second modification, in contrast, is viable. It would treat the PlotRegion as yet another parameter to the FunGraph. However, there is a differentImage 1.8.4mismatch: The function and the axis are designed as immutable value objects, while the PlotRegionImage 11.3.3.1 is an active information holder. Passing the region around as a mere value then introduces an inconsistency there.


11.3.3.7 Roles and Changeability

The objects treated previously have had concrete tasks that they fulfilled themselves or delegated to others. We will now venture a bit further afieldImage 3.2.1 and try our hand at (behavioral) abstraction: What if suddenly not one, but several possible objects can fill a place in the overall network and take on the associated responsibilities? In other words, what if we start designingImage 11.1 Image 211 roles, rather than single objects (see Fig. 11.3 on page 576)? Then we start collecting sets of related responsibilities without deciding immediately which object can fulfill them.


Use roles to keep the concrete implementation of a task exchangeable.


The function plotter contains an instance of such a challenge. The user can switch between different kinds of axes at runtime (see the lower-right corner of Fig. 11.8). Each axis looks slightly different on the screen, but the essential responsibility is clear: to overlay the display area with a coordinate system. Also, we make an axis responsible for describing itself. At the language level, we render a role as an interface, shown in the next code snippet. The getDescription() method is clear, but what are suitableImage 7.8 parameters to paintAxis()? For technical reasons, we need the graphics context GC, and we also pass the screen area and the plot region, because the axis painter will need them for scaling its drawings. The first parameter fun Graph is introduced because in callbacks it is usually sensible to pass theImage 12.3.2 context in which they take place.

fungraph.AxisPainter


public interface AxisPainter {
String getDescription();
void paintAxis(FunGraph funGraph, GC g, Rectangle screenRect,
PlotRegion region);
}



Image We render roles as interfaces because then an object implementing the interface implicitly declares that it will fulfill the associated responsibilities faithfully. The very same promise is expressed by the Liskov Substitution Principle as well as by contractImage 3.1.1 Image 6.4 inheritance: When an object overrides a method, it must honor the contract associated with that method; that is, it inherits the contract with the method.



Image The STRATEGY pattern anticipates the design-level concept of roles at the languageImage 1.3.4 level: Because the system contains alternative implementations of some task, we abstract the commonality into a common superclass. In designing roles, we start from the top and first ask what needs doing, before we start wondering who will eventually do it.



Use roles to defer decisions about the concrete implementation.


Roles enable concrete objects to be exchanged later on. This is useful not only if there are different implementations, but also if the only implementation should be exchanged later on. In the example, we were unsure how bestImage 11.3.3.3 to represent a formula. We therefore introduced an interface that captures just the responsibility that the object can somehow evaluate itself.

computation.PlottableFunction


public interface PlottableFunction {
String getDescription();
double funValue(double x) throws Undefined;
}



Roles very often specify only aspects of the object’s overall behavior.


In both cases shown previously, the concrete implementation objects have the only purpose of fulfilling the responsibilities associated with a role. In the majority of cases, however, the concrete object has far more comprehensive responsibilities—those from the role capture only one aspect of the overall behavior. An observer, for instance, is capable of receiving stateImage 2.1 change notifications, but that is not its purpose; in fact, it needs the notifications only as auxiliary information precisely for fulfilling a different purpose.

In fact, it can be useful to start designing roles and then to create objectsImage 211 filling these roles with concrete behavior in a second step. This approach will yield fewer dependencies between the objects so that the overall design remains more flexible and will accommodate changes more easily. We have seen this idea already in the context of client-specific interfaces. A differentImage 3.2.2 perspective is offered by the Dependency Inversion Principle (DIP).Image 11.5.6

11.3.3.8 Neighborhoods: Subnetworks of Objects

Image 263One further element of responsibility-driven design remains to be discussed: neighborhoods. Very often, single objects are too small as units of design, because many objects have to collaborate to achieve a task of any size. When trying to flesh these objects out from the start, we can easily lose track of the application’s overall structure.


Neighborhoods are groups of objects working on a common task.


Neighborhoods are groups of objects that collaborate closely to achieve a common overall goal. Fig. 11.10 gives the underlying structure. In the central, dashed area, a close-knit network of objects works on a task, while the outer objects consume their service through relatively few well-defined channels.

Image

Figure 11.10 Neighborhoods


Objects collaborate more closely within a neighborhood.


The function plotter example is almost too small to demonstrate neighborhoods properly. However, the group of objects implementing the central plot area in Fig. 11.8 (on page 594) are certainly connected more closelyImage 11.3.3.3 among themselves than with the rest of the system: TheFunGraph usesImage 11.3.3.7 different AxisPainters for overlaying a grid, a Scaler for mapping coordinates from the screen, and a PlotRegion to maintain the coordinates to be displayed.

The outside world has to know next to nothing about these internal collaborations. The Scaler is package-visible and hidden altogether; the Plot Region and the AxisPainter are merely plugged into the FunGraph object. Furthermore, we can hide the exact nature ofAxisPainters by making their concrete classes package-visible and exposing just the interface. Then, the FunGraph can be asked to provide all possible choices, so that the main ApplicationWindow merely has to plug them into the ComboBox Viewer shown in Fig. 11.8.

main.ApplicationWindow.createContents


ComboViewer chooseAxis = new ComboViewer(entry);
AxisPainter[] axisPainters = funGraph.getAvailableAxisPainters();
chooseAxis.setInput(axisPainters);



Neighborhoods have a common overall purpose.


Neighborhoods lift the idea of responsibilities to groups of objects. Just as the Single Responsibility Principle demands that each individual object hasImage 11.2 a clearly defined purpose, so the objects in a neighborhood taken together have a common, more comprehensive responsibility. In the end, the idea of what a “single” responsibility really is depends on the degree of abstractionImage 11.2.2 chosen in expressing the responsibility.


Neighborhoods may simplify outside access through designated objects.


Neighborhoods also shield the outside world from the internal complexities of the contained subnetwork of objects. If the objects have few collaborations with the outside world, then the outside world has to understand only these few collaborations and can leave the remaining ones alone.

One step further, a neighborhood may choose to designate special objects through which all communication with the outside world is channeled. The FACADE pattern expresses just this idea: to shield clients of a subsystemImage 1.7.2 from its internal complexities.


Use modularization to enforce boundaries of neighborhoods.


Encapsulation at the level of objects means hiding the object’s internal data structures. Encapsulation at the level of neighborhoods means hiding the internal object structure. The benefits of encapsulation then transfer to the larger units of design: You can change the internals if you see fit, and it becomes simpler to understand and maintain the overall system.

Encapsulation for object structures is available at many levels. NestedImage 1.8.8 classes are usually private anyway, and package-visible (default-visible)Image 1.7 classes and methods can be accessed only from within the same package. OSGi bundles in Eclipse help you further by exposing packages to otherImage A.1 bundles selectively. Also, you may consider publishing only interfaces inImage 3.2.7 these packages and keeping the implementation hidden away in internal packages. The Eclipse platform uses this device throughout to keep the code base maintainable.

11.3.4 Summing Up

The presentation of the function plotter’s design has focused on the resulting code, because it is this code that needs to be developed and eventually maintained. Now it is time to look back and to evaluate our design: Does it lead to an overall sensible structure?

Fig. 11.11 shows the principal objects in the design. The dashed objects are roles. The arrows with filled heads indicate access or usage. The arrows with open heads, as usual, denote subtyping. The dashed arrows mean that an access takes place, but only through an OBSERVER relationship or event notifications. Both dashed objects and dashed arrows therefore indicate aImage 12.1 looser kind of relationship that does not entail an immediate dependency.

Image

Figure 11.11 Overall Structure of the Function Plotter

Let us then check our design. The FunGraph implements the main functionality. It relies on several direct helpers: A PlottableFunction to obtain the values to be drawn and an AxisPainter to draw the coordinate grid according to the user’s choice. Both are roles—that is, interfaces at the code level—to keep the core functionality flexible. The Plot Region, in contrast, is an integral asset for drawing functions, so it is a concrete class. However, the actual drawing is insulated from the challenges of coordinate transformations by accessing the PlotRegionthrough a Scaler. There are two choices of coordinate grids, both of which also access the Plot Region through a Scaler.

The remainder of the user interface components are grouped around the FunGraph. A RegionSelector contains the widgets to manipulate a Plot Region. Any changes are sent directly to FunGraph as notifications. This arrangement follows model-view separation, with thePlotRegion as the model. However, the RegionSelector is not a full view, because it does not listen to changes of the PlotRegion. This could, however, be accomplished easily if necessary.

The ComboBox for choosing the coordinate grid and the FormulaField are handled differently. Both report their values to a Mediator, which decidesImage 1.3.4 what to do with them. In this case, it parameterizes the FunGraph.

The parsing of formulas, because of its complexity and a preexisting library, is isolated in one “corner” of the design: Only the FormulaField knows about parsing, and only the Mediator knows that behind a PlottableFunction, there is actually just a simple Expr object.

This summary is actually more than a convenience for the reader: Whenever you finish a design, it is useful to try to tell the overall story, to check whether the reasoning makes sense when told in a few words. If you are able to accomplish this, then the design is simple enough and understandable enough to be actually implemented.

11.4 Responsibilities and Hierarchy

Hierarchy and abstraction are at the heart of object-oriented programming. We have already examined at the language level the fundamental principlesImage 3.1.1 governing the use of inheritance and interfaces. The Liskov Substitution Principle requires that an object of a subtype—a derived class or a class implementing a given interface—can be used in all places where the super-typeImage 3.2.1 is expected. Behavioral abstraction complements this with the idea that superclasses and interfaces usually specify general reactions whose detailsImage 6.4 can be implemented in different ways. Finally, the idea of contract inheritance makes these concepts precise by relating them to the pre- and post-conditions of individual methods.

It now remains to ask how hierarchy relates to responsibilities. The connection, of course, rests on the fact that responsibilities always specify aspects of an object’s behavior and that hierarchy is one way of structuring this behavior. In expressing the earlier principles in terms of responsibilities, we will find that the new terminology also offers a new perspective on the previous ideas.

Before we start, we emphasize that inheritance and subtyping are not central to the idea of responsibilities. In fact, responsibility-driven design asks only what objects do and deliberately ignores how they have beenImage 1.4.1 created: It is irrelevant whether a method’s implementation is inherited or whether we need to introduce and implement certain interfaces to satisfy the compiler that a given method call is legal. Responsibilities help us free our minds from these implementation details to focus on the design itself.


All classes and interfaces have responsibilities.


All classes have responsibilities, including those used as base classes. Likewise, all interfaces can be assigned responsibilities, because in the end they must be implemented by objects—an interface is merely a restricted way of accessing an object. As pointed out earlier, responsibilities relate to the concrete objects, independent of the technical questions of inheritance and subtyping.


Classes take on the responsibilities of their super-types.


The Liskov Substitution Principle demands that clients can work with subclasses where they expect one of their super-types. This means that a class must take on all responsibilities of its super-types: When a client looks through the responsibilities of a super-type, it expects that the concreteImage6.4 object will fulfill them. This is analogous to contract inheritance, so that we could say that responsibilities are inherited, too.

For instance, every SWT Control must notify interested listeners aboutImage 7.8 mouse movements, so a Canvas, which is used for custom widgets, must report mouse movements as well.


Roles capture behavioral abstraction.


Image 11.1We said earlier that roles are usually rendered as interfaces at the implementation level. Interfaces serve many different purposes, among themImage 3.2.1 Image 1.4.1 behavioral abstraction: The precise reaction of an object to an incoming message is left unspecified, so that each concrete class is free to react suitably.

Roles, as collections of responsibilities, can also exhibit this kind of abstraction. All that is necessary is to phrase the responsibilities in general, abstract terms. For instance, an AxisPainter in the function plotter exampleImage 11.3.3.7 is responsible for painting a grid over the function graph, but that job description is so abstract that it allows for different concrete interpretations.

Even without deliberate generalization in the phrasing of responsibilities, roles should always be understood as behavioral abstraction. The intentionImage 11.1 of roles is that many different objects will fill them, so that it is usually not safe to presume any concrete reaction.


Inheritance allows for partially implemented responsibilities.


We saw in Part I that it is quite common for a class to leave some of its methods unimplemented. A class might leave the definition to the subclassesImage 1.4.10 altogether, or it might leave out specific steps in a mechanism through TEMPLATE METHOD. In both cases, the superclass fixes responsibilities,Image 1.4.9 but delegates their concrete implementation to the subclass.


Inheritance can structure responsibilities.


Of course, a subclass can also take on new responsibilities quite apart from those of its superclass: An SWT Text input field enables the user to enter text; a Button presents a clickable surface. Both show suitable indications of mouse movements and keyboard focus. None of this, however, belongs to the responsibilities of their common superclass Control.

With each level of the inheritance hierarchy, new responsibilities can be stacked upon the old ones from the superclass. Inheritance therefore helps to structure a class’s responsibilities further, with the intention of sharing the responsibilities that are introduced higher up among many different classes.


Protected methods introduce hidden responsibilities.


Protected methods enable a special form of collaboration between a subclassImage 3.1.2 and its superclass. On the one hand, the superclass can offer servicesImage 1.4.8.2 that only its subclasses can use. On the other hand, through method overridingImage 1.4.11 and abstract methods, the superclass can request services from itsImage 1.4.9 subclasses. Taken together, these mechanisms enable classes along the inheritance chain to have “private” responsibilities that are not visible to other objects.


Inheritance can be used for splitting responsibilities.


Taken together, the three previous points show the power of inheritance: It enables us to split the implementation of responsibilities of a single object into separate pieces of source code. At each step, the responsibilities areImage 11.2 self-contained and, ideally, small. As a result, the overall code base becomes more understandable, maintainable, and possibly reusable.

Image 11.1A larger example can be seen in the context of the class NewWizard. It uses a NewWizardRegistry to gather supported file types from the different plugins. That class is built in two inheritance steps, because different kinds of wizards are registered within the system. TheAbstractWizard Registry simply keeps registered wizards in a list. Its subclass Abstract ExtensionWizardRegistry adds the responsibility of gathering these, butImage 12.3.3 delegates the choice of the the type of wizard and their source extension point to its subclasses. TheNewWizardRegistry then takes on this remaining responsibility.


Be precise despite the abstraction.


Inheritance and hierarchy usually involve abstraction: The super-types specify responsibilities that comprise more cases, because they are implemented differently in the different subtypes. It is important not to confuse “abstract” with “vague” or “imprecise”: If an abstract responsibility is imprecise, then it is unclear whether a subtype validates the Liskov Substitution Principle and whether clients can rely on its behavior. The formulation of a responsibilityImage 6.4 must always be precise enough to allow precise reasoning about whether a subtype fulfills it properly.

11.5 Fundamental Goals and Strategies

It is really hard to say what makes a good designer and good designs. Of course, designs should be simple and easy to implement; they should follow established conventions and patterns; they should accommodate changes in requirements gracefully, simplify ports to different platforms, and so on. At a personal level, the designers should be able to communicate with different stakeholders; they should have a clear grasp of the strategic goals and overall architecture to make informed decisions; and so on. But these criteria are all rather abstract, and their application may depend in many cases on personal preferences and biases, as well as on one’s own previous design experience.

Conversely, it is quite straightforward to say what makes a bad designer.Image 92(“Bad Smells”) Bad designers ignore the fundamental rules of their trade: They let objects access each other’s data structures; they have no clear idea of what each object is supposed to do; their methods can be understood only by reading through the code; and they commit a multitude of other sins. In short, they create designs that result in completely unreadable, throwaway implementations.

This section gathers several fundamental requirements related to design. They have appeared as specific aspects and arguments in previous discussions, but now we switch perspective and put them at center stage. We also add more conceptual background, which would have been misplaced in the concrete previous settings.

11.5.1 Information Hiding and Encapsulation

The most fundamental principle of any software design—not just object-oriented design—is certainly information hiding. The size and complexity ofImage 205 modern software simply demands that we split up the implementation into chunks, or modules, which can be worked on independently by different team members or different teams. This is possible only if each team can focus on its own contribution and does not have to know the technical details of the other teams’ modules. The information about what goes on in each module is hidden from the world at large.


Information hiding goes beyond encapsulation.


Information hiding is not simply another name for encapsulation. EncapsulationImage 216(§7.4,§7.6) Image 1.1 is a language-level feature that controls access to part of a module’s definition. For instance, the Java compiler will throw an error when you try to access an object’s private fields from outside its own class. That is certainly very useful, in particular if some other team includes a tech guru who likes to tinker with other people’s data structures.

Information hiding means much more than just making an object’s internals inaccessible. It means restricting the knowledge that other teams may have—or need to have—about your objects. So the information hidden is not the object’s data, but your own team’s information about why and how the object works.


Image Few software engineers, and indeed only a small part of the literature, will be so severe with their distinction between information hiding and encapsulation. Often the terms are used interchangeably, or encapsulation is used to encompass information hiding, because encapsulation, as we will see, is not useful on its own. So when someone says to you, “I have encapsulated this data structure,” that statement usually implies “You’re not supposed to know I used it at all” and “You can’t hold me responsible if I trash the code tomorrow and do something entirely different.” We make the distinction here mostly to highlight the extra effort a professional developer has to spend beyond making fields private. We will use “information hiding” when discussing design questions and “encapsulation” when discussing the implementation’s object structures.


Suppose, for instance, that your team is responsible for your software’s AuthenticationService, as shown next. The method authenticate() enables clients to check whether the given user can log on with the given password. Sometimes, other modules may need a complete list of users—for instance, to display them to administrators. It is OK to have a method get AllUsers() for this purpose:


public class AuthenticationService {
private List<User> users = new ArrayList();
public boolean authenticate(String user, String password) {
. . .
}
public User[] getAllUsers() {
return users.toArray(new User[users.size()]);
}
. . .
}


Image 1.3.3Encapsulation is about technical security: The method getAllUsers() copies the internal list to prevent other modules from smuggling in newImage 1.7.1 users. We can also make the setters in class User package-visible to prevent other modules from changing user names and passwords.

Information hiding does more. It asks: What can the other modules learn about the AuthenticationService? The method getAllUsers() tells them that there is a global list of system users. Well, that was rather obvious anyway. Or was it? As soon as you start linking to different LDAP or ActiveDirectory servers, rather than finding users only in a local database, you will not be able to assemble that list efficiently, so perhaps you have already told too much, because you have barred yourself from one future extension to the system.

Information hiding is also about the things you do not tell your colleagues. Suppose that for efficient lookup, you decide to keep the internal list users sorted by the user names. Within your class, you can use binary search to find a user. Great. But it would really be unwise to meet an other team over coffee and spill the beans: The next thing you know, a different team might start using binary search on the result of getAll Users()! Once again you have lost the chance of ever changing the “internal” order of users, because the information about this order is no longer internal—it is no longer hidden information.


Image Image 4.1Information hiding also sheds new light on contracts and their usefulness in system design. The assertions you state in pre- and post-conditions are all that other modulesImage 4.1 will ever learn about your module’s behavior. (Recall that the class invariant is a private matter and is not disclosed.) If the post-condition of getAllUsers() in the example states that the result is sorted by the user names, then this knowledge can be used by callersImage 4.7 for their reasoning. If it says nothing about the sortedness, the caller must not make anyImage 4.1 assumptions in this direction (and your method does not have to prove sortedness).



Use encapsulation to express information hiding.


So information hiding is more than encapsulation, but the two are definitely related: Both are about keeping parts of your own implementation hidden, only at different levels of reasoning.

However, information hiding always comes first: You start by deciding what others may know about your implementation. This means that you have to formulate an information policy regarding your design and implementation decisions. Once you have established this policy, you must figure out how you can enforce it at a technical level through access restrictions. Not every detail can be enforced, such as the knowledge about the sortedness in the preceding example. However, modern programming languages are built with decades of experience in information hiding, so they offer pretty expressive mechanisms. It is worthwhile studying them in some detail. Here are some Java examples beyond the obvious private fields and methods: Package visibility keeps information between a set of relatedImage 1.7.1 classes, protected keeps functionality confined to a class hierarchy, andImage 1.4.8.2 interfaces can be used to hide away concrete classes and class hierarchies.Image 3.2.7 Image 12.2.1


Information hiding keeps the implementation changeable.


You recognize good information hiding in an object or a subsystem if youImage 11.3.3.8 could change the implementation overnight without telling anyone and the system would continue to work as if nothing had happened.

The ability to throw away and recreate the implementation is essential to productivity, because it enables you to use the simplest thing that couldImage 28 possibly work until you find that it is not good enough after all. Without information hiding, you would have to think about and work hard on an optimal solution for every single object and every single task, because you would never be able to amend your choice later on.

In fact, this changeability is the main argument in Parnas’sImage 205 seminal paper on modules and information hiding. The information that must really be hidden is the design and implementation decisions that you make, becauseImage 11.2.3 Image 12.1.1 Image 12.2 then you can revise these decisions later on, during software maintenance, without breaking the system.


Assign responsibilities to match the goal of encapsulation.


The question is, of course, If encapsulation is about the implementation, why is it discussed so late in the book, rather than in Part I? The answer is simple: You have to prepare encapsulation by making a suitable assignment of responsibilities. If one object holds the data that another object works on, then there is little chance of encapsulating the choice of the data structure. If a class has so many responsibilities that it becomes large, then encapsulation is not useful, since it applies only between classes. These things are also known as “bad smells.”Image 92

Image 11.2The Single Responsibility Principle counteracts many of these dangers. It ensures that classes remain small and have a well-defined purpose that can be expressed in one or two sentences. You can also easily recognize if two objects perform tasks that are too closely related or even duplicates.

If the Single Responsibility Principle guides you toward building small objects, then the next design question arises immediately: How can you come up with efficient collaboration steps that still keep internal objects reallyImage 1.8.1 private? Perhaps you may even have to introduce a special information holder object that acts as a black box to the outside world.


Choose neutral data structures for argument and return types.


One step further, you may be in danger of leaking information about the internal data structures even if you are not leaking the data structures themselves. If your internal field holds an ArrayList, for instance, that fact should not show up in method signatures. Better to use a genericList, which also gives you the ability to wrap your internal list by Collections.unmodifiableList(). Or you might go for a copied array, created by toArray(), in the getter.


Image Image 12.2.1.1Of course, the preference for neutral data types is not to say that you pass aroundImage 172,44 only values of primitive types. That would be disastrous: It becomes hard to understand the contracts; the names of types cannot convey meaning; the compiler cannot check whether the right kind of data is passed; the data cannot take on responsibilities. Just don’t choose overly concrete types that give away your implementation, that’s all.



Make methods reflect conceptual operations.


Saying that you should not reveal the object’s internals through its method signatures is a negative guideline. Here is a positive, and more constructive,Image 11.2.2 one: If you are able to express an object’s purpose concisely, then chances are you have taken the mental leap toward suppressing any details of possible implementations. Choosing the method signatures to hide these details then comes naturally: The methods are merely the technical way to gain access to an object’s services.

We saw the same requirement earlier, in the approach of design-by-contract.Image 4.1 There, we demanded that contracts be understandable from the object’s specification, without considering its implementation. This wasImage 4.2.2 captured in the Precondition Availability Principle.


Take efficiency as a secondary aim.


The goals of information hiding and encapsulation often conflict with the goal of efficiency. Handing out references to the internal data structures is, of course, the access path that makes for the lowest runtime. When thinking about encapsulation, we should therefore always be aware thatImage 1.1nowadays the most costly resource is development time, not execution time. Furthermore, the really critical paths in terms of efficiency will show up only under load, where they can be analyzed using a profiler. And even if you do then find that an object with perfect encapsulation is on such a critical path, you can often improve its performance by indexing and caching—thatImage 1.3.6 is, precisely those internal changes enabled by proper encapsulation.


Encapsulation is the indispensable first step toward changeability.


The changes enabled by encapsulation are entirely local. In essence, you can revise your decisions about how a single object will dispatch its responsibilities. Many more considerations, also about inter-object structures, applyImage 12.1 Image 12.2 when you wish to keep the software flexible at a larger scale. Yet without achieving encapsulation properly first, these further measures cannot be approached at all: In any case, there will be some object or interface behind which the changeable part of the system remains hidden from the clients. Gaining experience with achieving proper encapsulation in concrete situations is a prerequisite for successful design in the large.

11.5.2 Separation of Concerns

The main point of design is to lend structure to the implementation. Without design, the implementation will look like one huge desert of code, where interfaces between objects are defined sloppily and each line potentially interacts with every other line. Separation of Concerns is the principle thatImage 78(Ch.27) counteracts such disastrous outcomes. The goal is to break down the overall problem into various aspects to keep the solution intellectually manageable. Dijkstra, who first introduced the concept, has put this eloquently:Image 78(p.211)

To my taste the main characteristic of intelligent thinking is that one is willing and able to study in depth an aspect of one’s subject matter in isolation, for the sake of its own consistency, all the time knowing that one is occupying oneself with only one of the aspects. The other aspects have to wait their turn, because our heads are so small that we cannot deal with them simultaneously without getting confused. [ . . . ] I usually refer to [this] as “a separation of concerns” [ . . . ].

In our experience, unfortunately, many programmers have trained themselves to be able to keep a huge amount of detail in their heads at the same time, and they pride themselves in being able to do so. Overcoming this love for detail and retraining oneself to practice Separation of Concerns can be challenging, but it is necessary to handle large and complex systems.


Implement distinct concerns in distinct parts of the software.


Any sizable piece of a software project will involve many different aspects. For instance, it will read and write data, perform computations on the data, access the network, and interact with the user. In many cases, it is useful to design the software such that each aspect, or concern, is handled by an entirely different subsystem.

Image 9.1One instance has been seen in model-view separation: The business logic and the user interface have so vastly different characteristics that it is best to keep them separate (see Fig. 9.1 on page 445). Likewise, it is useful toImage 11.3.3.2 Image 1.8.7 Image 1.5.2 designate a “boundary” area where the software does all of its I/O and scrupulously checks all interactions with the outside world. Conceptually,Image 4.6 this will keep the remainder free from checking the validity of data, so that the business logic can be implemented without distractions.


Separation of Concerns can suggest new subsystems.


Separation of Concerns acts as a repelling force between pieces of code. When you are implementing, or designing, one module with a clear purpose, and you find suddenly that you are thinking about an aspect that does not fit the purpose, it is time to push that aspect out into a separate module.

For instance, when your business logic needs to do logging for documenting its safety-critical decisions, you may find logging code splattered all over the nice, clean business logic code. It is much better to introduce a logging module that offers just the interface that the business logic needs to create the required documentation entries.

Image 201Another example may be access restrictions—for instance, in web applications. Security is so important that one cannot force authors of single pages to hand-code the security checks. It is better to implement a generic protection mechanism, possibly with declarative syntax for writing up constraints.


Separation of Concerns encourages the Single Responsibility Principle.


Separation of Concerns also works in the small, at the level of individualImage 11.2 objects. Here, it complements the Single Responsibility Principle. Suppose an object is responsible for downloading a file from the web. Then it should probably not also be responsible for determining a viewer to display the file to the user. In this way, you will arrive at objects with a clear-cut purpose, just as recommended by the Single Responsibility Principle.

From a more general perspective, Separation of Concerns suggests to push apart those elements of functionality that are only vaguely connected. This step leaves together those elements that have a strong logical relationship.Image 12.1.3 Separation of Concerns therefore leads to a higher cohesion of objects, modules, and subsystems.


Splitting concerns creates a need for communication.


Separation of Concerns is a useful and central principle, but it also comes with a few downsides. The first one is rather obvious: Because the different aspects of an overall goal are distributed between modules, these modules will have to communicate to achieve that goal.

If the necessary communication is complex, then nothing is gained. For example, the MODEL-VIEW-CONTROLLER pattern is often implemented in the DOCUMENT-VIEW variant, because the View and Controller share soImage 9.2.8 many assumptions about the concrete screen display that it is often more sensible to merge them into a single object, leading to the DOCUMENT-VIEW variant.

Alternatively, the communication can happen through well-defined interfaces, which helps with encapsulating the details of the subsystems, soImage 11.5.1 Image 11.2.3 that maintenance is simplified. The extra effort involved in enforcing Separation of Concerns may pay off later on.


Image Sometimes, the necessity of communication can lead to new and useful concepts. In compiler construction, for instance, one separates the concerns of parsing andImage 2 semantic analysis, such as type checking, in the front-end from the concern of code generation in the back-end. To do this, one has to introduce intermediate languages to pass information between the different phases. To make the compiler reusable, there is often one intermediate language that abstracts over the physical details of a specific CPU type, such as by assuming an unlimited number of registers. Optimizations are much easier in this intermediate language than they would be in the final machine code.



Watch out for duplicated or complicated functionality.


Another undesirable result of splitting a piece of larger functionality into separate concerns is that some functionality may have to be duplicated or newly introduced. This often happens because slightly different algorithms are required for similar data stored in different modules.

In the context of the MODEL-VIEW-CONTROLLER pattern, for example, large documents can be handled efficiently only by incremental screenImage 9.4.3 updates. This means that the concern of “determining the changed model parts” has to be implemented twice: once at the level of the model itself and once to find the corresponding screen representation. For the second task, it might be necessary to keep a structure analogous to that of the model. An example can be found in the tree-structured edit parts of Eclipse’s Graphical Editing Framework.Image 214


Watch out for cross-cutting concerns.


Some concerns do not lend themselves to being pushed out into a separate module. While these concerns do occur in many different places in the application, they are tied in with the local context so much that extracting them feels like doing violence to the code.

For instance, if an application accesses a relational database, it will have to create SQL statements in many places. The concern of “querying the database” is a cross-cutting issue. The obvious choice is to introduce a separate module that contains a method for each occurring SQL statement. However, this disrupts the original source code and does not help at all: A maintenance programmer can understand the code only by jumping to the new module.


Be aware that concerns are a matter of perception.


Image 267Looking a bit more closely reveals the concern of producing syntactically valid SQL code for a specific query: Introducing a module that takes care of syntactic issues will simplify the remainder of the application. We might even rethink the entire idea of “accessing the database” by employing anImage 129 Image 5.4.9 object-relational mapping: The mapping layer can encapsulate the concern of database accesses altogether, while the application deals with only data access objects provided by the mapping layer.

In the end, the question of what a “concern” really is does not have an absolute answer. Designing also means identifying and phrasing subproblems, and identifying concerns to be treated separately is no different. Just as in the case of the Single Responsibility Principle, it is a good idea toImage 11.2.1 try to tell a self-contained story about a candidate concern. If you succeed, chances are you will also find a self-contained implementation in a separate module.

11.5.3 Compositionality

When developing object-oriented software, we frequently find that we areImage 189 applying a principle from the area of mathematical proof: compositionality. Compositionality in that context denotes the ability to finish subproofs about parts of a composite entity and then to derive in a single step, without the need for further insights, that a statement about the entity itself holds. The subproofs can simply be composed.

You are wondering what this might have to do with programming? HereImage 209 is a first example: Java’s type system. Type systems are defined such that one can derive the type of an expression from the types of its subexpressions, without reexamining the subexpressions. For instance, knowing that hexRep is a method that takes a long and returns its hexadecimal representation as a String and that a+b yields a long value, we deduce that hexRep (a+b) must be a String.

We will now explore how the idea of compositionality links to object-oriented design in that it integrates previous strategies and goals into a common perspective.


Compositionality lets systems be created from atomic pieces.


Compositional reasoning always involves finding simple rules for combining partial results in a well-defined manner. Just think of the power of objects that can be combined so easily to achieve larger functionality! To create new software, you would simply take a few objects with clearly delineated behavior and “click them together” to achieve your goals. We will call objects that support such constructions compositional.

As a simple example, Java’s I/O library features different InputStreamsImage 2.4.2 that can be combined to decode special file formats, including encrypted and compressed streams. Likewise, SWT user interfaces are created by combiningImage 7.1 atomic and compound widgets, and placing them on the screen using layout managers. These examples basically use object composition toImage 2.2 achieve the purpose.

Here is a more advanced example. In the context of graphical editors, one can often modify short texts by direct editing: One clicks on a text, a small pop-up appears on top, and one types the new text. Eclipse’s GraphicalImage 214 Editing Framework provides a classDirectEditManager for implementing this behavior. The class links to its context by three points: A Graphical EditPart is an element in the drawing representing a specific model element; a CellEditorLocator places the pop-up, usually relative to the edit part; and aCellEditor, such as TextCellEditor from the JFace framework, is used as the actual pop-up. The edit part, the cell editor, and perhaps also a generic cell locator can be reused. The DirectEdit Manager ties them together into a small network of objects that creates the overall user experience.


Compositionality requires the Single Responsibility Principle.


One aspect of compositional objects is that tasks are distributed among several objects, each of which makes a well-defined and small contribution. This goal is shared with the Single Responsibility Principle.Image 11.2

However, compositionality is more. It focuses on the single objects and seeks to find responsibilities that are meaningful in themselves, independent of the particular network in which the object is conceived.


Compositionality entails information hiding.


The attraction of compositional proofs is that subproofs remain valid independent of their context, and they never need to be reinspected when combining them into a larger proof. Objects can be compositional only if they can be understood without looking at their internals, which is precisely what information hiding is about.Image 11.5.1


Compositionality requires clear and general contracts.


The idea of “clicking together” objects works as intended only if the objects’Image 4.1 interfaces are defined precisely, using contracts. Without clear contracts, objects cannot be combined without reconsidering their internal behavior, which is precisely what compositionality discourages.

Compositionality goes beyond contracts in that it demands general interfaces that are useful in many different situations. The JFace TextCell Editor in the direct editing example was never meant to be used in the context of graphical editors, but because its interface does not make specific assumptions about its underlying widget, it works for the GEF as well. TheImage 101 Image 12.3.4 classical PIPES-AND-FILTERS pattern is so powerful because the filters, as the units of data processing, share a common data transfer format, so that they can be combined freely.


Compositionality fosters reuse.


If objects “click together” well in one context, each implements an atomic contribution, and their interfaces are general enough, then it becomes moreImage 12.4 likely that individual objects become reusable.

However, compositionality does not require aiming at reuse. Even if objects fit well only within a single context, one still gains the other benefits described earlier.

11.5.4 Design-Code Traceability

Image 233In traditional software processes, design and implementation have been seen as consecutive steps: Once the design is complete, it remains onlyImage 28,221 to implement it faithfully. Later on, the agile movement realized that the invariable problem of discovering design flaws too late is responsible for many failed software projects and broke up the strict sequence in favor of truly iterative development.


The design must be reflected in the concrete code.


The goal is to create code that matches the design. We design to create an overall structure; to plan the implementation; to distribute work among teams; to communicate about our strategy; to maintain an overview of the code despite its technical complexity; to come back to the code during maintenance. All of this works best if we can point to some place in the designImage 24,7 and find its concrete rendering in the code immediately. Maintaining mappings between different artifacts within the software development process is commonly called traceability.


Anticipate the implementation while designing.


One of greatest dangers when first designing larger applications is that the design itself looks useful and sensible, but then its implementation turns out to be tricky or cumbersome. As the saying goes, “The proof of the pudding is in the eating.”

The only realistic countermeasure is to think about the implementationImage 263 in quite some detail while designing. During this process, one constantly switches between high-level planning and low-level implementation, until one is satisfied that everything will be all right.

Coplien has introduced the useful concept of multiparadigm design. HeImage 71 complements the standard approach of analyzing the application domain with the proposal of analyzing the solution domain with the same rigor. The solution domain comprises the language features available for the implementation. The goal is to find the most natural way of mapping the structure of the application domain to the available constructs of the solution domain.


Prefer short-term plans and validate them immediately.


Design-code traceability requires a lot of implementation experience to get it right the first time: One has to express the right structures in the code so that all technical details will fit in. Whenever you feel that the design of some subsystem or neighborhood is fixed, you might want to start implementing the design immediately, or at least write down the public API so that the network of objects compiles. Test-first comes in handy, becauseImage 5.2 you not only see the static structure, but also can check out whether the design covers all functionality by suitable mechanisms.Image 11.1


Evaluate design decisions based on the resulting code.


In fact, the respective merits of alternative designs very often show up only in the implementation—for instance, in the amount of overhead required to achieve some desired generalization. When faced with a complex problem in an area where you have little implementation experience, it might be useful to write down some code for several alternatives to get a feeling for their feasibility.

11.5.5 DRY

Implementing the same functionality in different places is usually a bad idea: Development takes longer and if you find bugs or otherwise have to alter the functionality, you have to track down all the relevant places. Doing this is called “shotgun surgery” and it indicates that your design is not yetImage 92 optimal. The positive formulation is simply this: “Don’t repeat yourself”Image 172 (DRY). Another one is “once and only once.” So far, so obvious.


DRY is more complicated than it seems: It requires structure.


Copy-and-paste programming is an incredibly popular method of development. Indeed, many pieces of functionality are so complex that we are happy and maybe lucky to get them right in one place. If we need the same or similar functionality somewhere else, we just copy the code and adapt it to the new context.

The reason for this deplorable state is that it takes thought and diligenceImage 11.1 to come up with an object that is truly responsible for a task and solves it once and for all. You have to define the task precisely, create an API, and make the object accessible wherever its functionality is required. If you are not sure of the task in the first place or, in the more likely case, do not have the time to work it out in detail, DRY is incredibly hard to achieve.


Know the techniques for factoring out functionality by heart.


The first step toward achieving DRY is to know as many technical tricksImage 71 of achieving it as possible: Once you know the solution space to a design problem, the design problem becomes much more tractable. For DRY, you should be familiar with object-oriented techniques that help in factoring outImage 1.4.8 Image 1.8.6 Image 1.8.2 functionality: Simple methods or objects can capture algorithms, or maybeImage 1.8.5 the functionality can be seen as a stand-alone service; through inheritance,Image 3.1.3 Image 3.1.4 a class can provide common behavior to clients, protected helper methodsImage 1.4.9 to its subclasses, or larger mechanisms through the TEMPLATE METHOD pattern. The more you become aware of these structures and the more you practice them, the easier it becomes to avoid code repetition. Copy-and-adapt is an effort, too, and if you can think of an alternative quickly, you will use it.


Design well-known parts carefully in a bottom-up approach.


When focusing on technical solutions only, you are likely to end up with code that is not understandable. The goal is not to write nifty object-oriented code, but rather to find an assignment of responsibilities so thatImage 11.2.1 Image 12.1.3 each class has a clear purpose.

The best bet for achieving DRY is careful design. If during design with CRC cards you find you have objects that are somehow similar, you can probe their similarity and express it as a separate object without much effort. If you do not succeed during design, you will never succeed during coding, where you have to pay attention to all sorts of technical constraints to avoid breaking the software. Work out the similarities you recognize at once.

Image 11.1 Image 11.3.2 Expressing similarities in objects is often a bottom-up activity. Design usually starts from the use cases to make sure all requirements are covered. When expressing similarity, in contrast, you go away from concrete goals and ask yourself which stand-alone piece of functionality would help to achieve these goals later on. Very often, this involves looking out for functionality you have already implemented somewhere else and know to be useful.

For instance, many dialogs in Eclipse are of the filter-and-select variety: The user types some name, and the system provides a list of choices quickly. Since the designers recognized this similarity, they have provided a FilteredItemsSelectionDialog that you can adapt to your needs by subclassing. It uses TEMPLATE METHODS to provide a powerful infrastructureImage 1.4.9 that supports your own implementations.


Refactor for the first (or second) copy.


In practice, the need for copy-and-paste often arises from new requirements that are introduced after the software is deployed. In such a situation, you may wish to quickly cover a new use case by adapting the solution to a similar one you already have.

At this point, you should sit back: Is it better to copy or to refactor? Copying is quick now, but you might pay a heavy price for it during maintenance. Refactoring requires more effort now, but you may create structure and stand-alone functionality that will come in handy later on. The problem with the decision is the “maybe” part of this process: It takes some experience to foresee the future development.

Many authors propose the heuristic that it is OK to have two copies of some functionality, because maintaining two copies is likely to be more effective than doing the refactoring. However, as soon as you create a third copy, you should refactor the code and replace all three copies with references to the common implementation.

Our personal experience is that it is useful to think about a refactoring even for the first copy. Usually, the need for copying shows that your design is incomplete, because you missed an opportunity to introduce an object or method with a single responsibility. Introducing a stand-alone object clarifiesImage 11.2.1 the design, if nothing more. In particular, you can choose an expressive name that helps maintenance developers to understand the software.

The choice between the two strategies depends on many factors, such as the size of the required refactoring, your familiarity with object-oriented techniques, and your prowess with the Eclipse refactoring tools. By learning and practicing refactoring often, you will make the balance shift toward refactoring, which improves both your development discipline and the code you produce. DRY is really a long-term goal.


DRY touches on reusability.


One key point is that DRY is really reuse in the small. You recognize a similarityImage 12.4 and code the solution up so that it can be used in different places. As a result, the challenges of refactoring can also arise when applying DRY. For instance, we have used parsers for arithmetic expressions in several placesImage 2.3.3 Image 9.4.2 Image 11.3.3.1 throughout the book. Parsing is a simple enough and well-understood task,Image 2 so one should think we have achieved DRY on our first try. For historical reasons, which are so frequent in practice, this is not the case, and extensive refactoring and redesign were necessary to achieve DRY. Since it isImage 12.4.3 somewhat intricate, the story will be told later on.

11.5.6 The SOLID Principles

We finish this overview with a collection of desirable design goals and principlesImage 170,169 for classes due to Martin. Their initials form the fitting acronym SOLID:

Image 11.2SRP The Single Responsibility Principle is here expressed as “There shouldImage 11.2.3 never be more than one reason for a class to change.”

OCP The Open/Closed Principle states that “A module should be open for extension but closed for modification.”

Image 3.1.1LSP The Liskov Substitution Principle states that “Subclasses should be substitutable for their base classes.”

Image 3.2.2ISP The Interface Segregation Principle states that “Many client-specific interfaces are better than one general-purpose interface.”

DIP The Dependency Inversion Principle demands, “Depend upon abstractions. Do not depend upon concretions.”

We will not go into the implications of SRP, LSP, and ISP here because they have been treated already in detail. The remaining two principles, OCP and DIP, concern the higher-level structures created in the design process.

The OCP recognizes that the behavior of objects usually needs to beImage 11.5.1 changed at some point. Because their internals may be complex, however, these changes should be effected without modifying the actual source code. In essence, the existing behavior can be extended to cover new aspects atImage 1.4.11 specific points. The technical basis is polymorphism, which might also beImage 1.4.9 Image 1.3.4 exploited through a TEMPLATE METHOD or STRATEGY. In any case, we introduce general mechanisms so that objects can be adapted by adding facets of behavior without touching their original sources.

Image 11.2.3 Of course, the OCP involves a rather substantial design challenge: The possible points of change have to be anticipated in the original design. This means that the designer has to be aware of possible variants of the functionality currently being implemented and has to create a technical interface that fits all possible uses.

The other new principle, DIP, highlights an important consequence ofImage 12.1 dependencies: When an object A depends on another object B, then AImage 4.1 is likely to need changes if B changes. In a classical contract-based relationship between two objects, it is the caller that depends on the callee [Fig. 11.12(a)]: If the callee is forced to change its contract to accommodate new requirements, then the callee must be adapted as well.

Image

Figure 11.12 The Dependency Inversion Principle

However, when considering the overall architecture of an application, this is often unacceptable. Here, the caller implements some application-specific, high-level functionality based on some detailed service of the callee. It is then the high-level functionality that should determine the need for change, not the low-level details whose modifications might be somewhat arbitrary—for instance, if they reflect the underlying hardware or third-party code beyond our control.

The solution proposed by DIP is to introduce an interface and to let the high-level module define that interface [Fig. 11.12(b)]. When the low-level details change, we may have to introduce an adapter or develop some glue-code, but the overall application will remain intact. The dependency is effectively inverted, because now the lower-level functionality must follow the higher-level functionality.

We have already seen several applications of DIP. Most prominently, the OBSERVER pattern enables application objects to specify their mode of collaborationImage 2.1.2 with dependent objects: They send out notifications about state changes without making assumptions about how these notifications will be used. In a specific instance, the model in the MODEL-VIEW-CONTROLLERImage 9.2.1 pattern contains the business logic of an application, its most valuable part. That model remains insulated from changes in the rather volatile user interface.Image 9.2.2

The motivation for and assumption of DIP is that abstractions will be more stable than concrete implementations. Abstractions often capture time-proven concepts that have been applied successfully in different contexts, while the concrete implementations found by different development teams usually differ. By fixing the API of the concept in an interface and working only with APIs, one can make the overall application more stable.

Of course, DIP then incurs the rather challenging design problem of finding both the abstraction and its interface. Finding the abstraction requires a lot of literacy in computer science; defining the interface calls for a lot of experience with different implementation strategies.