The Principle of Designing from Context

Alexander tells us to design from context, to create the big picture before designing the details in which our pieces appear. Most design patterns follow this approach, some to a greater extent than others. Of the four patterns I have described so far, the Bridge pattern is the best example of this.

Refer to the Bridge pattern diagram in Chapter 9, “The Bridge Pattern,” (see Figure 9-13). When deciding how to design the Implementation classes, think about their context: the way that the classes derived from the Abstraction class will use them.

For example, if I were writing a system that needed to draw shapes on different types of hardware and that therefore required different implementations, I would use a Bridge pattern. The Bridge tells me that the shapes will use my implementations (that is, the drawing programs I will write) through a common interface. Designing from context, as Alexander would have, means that I should first look at the requirements of my shapes—that is, what am I going to have to draw? These shapes will determine the required behaviors for my implementations. For example, the implementations (the drawing programs) may have to draw lines, circles, and so forth.

By using commonality/variability analysis in conjunction with the context within which my classes occur, I can simultaneously see both the cases I must handle now and possible future cases. I can then decide how generalized I want to make the implementations based on the cost of extra generalization. This often leads to a more general implementation than I would have thought of otherwise, but with only marginally higher cost.

For example, when looking at my needs to draw shapes, I might readily identify lines and circles as requirements. If I ask myself, “What shapes do I not support with lines and circles,” I might notice that I would not be able to implement ellipses. Now I have a choice:

  • Implement a way to draw ellipses in addition to the lines and circles.

  • Realize that ellipses are a generalized case of circles and implement them instead of circles.

  • Don't implement ellipses if the cost is greater than the perceived gain.

The prior example illustrates another important concept in design: just because an opportunity exists doesn't mean it has to be pursued. My experience with design patterns is that they give me insights into my problem domains. However, I don't always (nor even usually) act on these insights by writing for situations that have not yet arisen. However, by helping me design from context, the patterns themselves allow me to anticipate possible variation because I have divided my system into well-behaved classes, thereby making changes easier to accommodate. Design patterns help me see where variations may occur, not so much which particular variations will occur. The well-defined interface I use to contain my current variations often contains the impacts of new requirements just as well.

The Abstract Factory is another good example of designing by context. I may understand early on that a factory object of some sort will be used to coordinate the instantiation of families (or sets) of objects. However, there are many different ways to implement it as follows.

Implementation … Comments …
Using derived classes The classic Abstract Factory implementation requires me to implement a derivation for each set that I need. This is a little cumbersome but has the advantage of allowing me to add new classes without changing any of my existing classes.
Using a single object with switches If I am willing to change the Abstract Factory class as needed, I can simply have one object that contains all of the rules. While this doesn't follow the open-closed principle, it does contain all of my rules in one place and is not hard to maintain.
Using a configuration file with switches This is more flexible than the prior case, but still requires modifying the code at times.
Using a configuration file with RTTI (runtime-type-identification) RTTI includes a way of instantiating objects based on the name of the object placed in a string. Implementations of this sort have great flexibility in that new classes and new combinations can be added without having to change any code.

With all of these choices, how do you decide which one to use to implement the Abstract Factory? Decide from the context in which it appears. Each of the four cases just shown has advantages over the others, depending upon factors such as

  • The likelihood of future variation

  • The importance of not modifying our current system

  • Who controls the sets to be created (us or another development group)

  • The language in use

  • The availability of a database or configuration file

This list is not complete, nor even is the list of implementation possibilities. What should be evident to you, however, is that trying to decide how to implement an Abstract Factory without understanding how it will be used (that is, without understanding its context) is a fool's errand.

How to make design decisions.

When trying to decide between alternative implementations, many developers ask the question, “Which of these implementations is better?” This is not the best question to ask. The problem is that often one implementation is not inherently better than another. A better set of questions to ask is, for each alternative, “Under what circumstances would this alternative be better than the other alternative?” Then ask, “Which of these circumstance is most like my problem domain?” It is a small matter of stopping and stepping back. Using this approach tends to keep me more aware of the variation and scalability issues in my problem domain.


The Adapter pattern illustrates design from context because it almost always shows up within a context. By definition, an Adapter is used to convert an existing interface into another interface. The obvious question is, “How do I know what to convert the existing interface to?” You typically don't until the context is presented (that is, the class to which you are adapting).

I have already shown that Adapters can be used to adapt a class to fit the role of a pattern that is present. This was the case in my CAD/CAM problem, where I had an existing implementation that needed to be adapted into my Bridge-driven implementation.

The Facade pattern is very similar to the Adapter pattern in terms of context. Typically, it is defined in the context of other patterns or classes. That is, I must wait until I can see who wants to use the Facade in order to design its interface.

Early on in my use of patterns I tended to think I could always find which patterns created context for others. In Alexander's A Pattern Language, he is able to do just that with patterns in architecture. Since many people are talking about pattern languages for software, I wondered, “Why can't I?” It seems pretty clear that Adapters and Facades would always be defined in the context of something else. Right?

Wrong.

One great advantage of being a software developer who also teaches is that I have the opportunity to get involved in many more projects than I could possibly be involved in as a developer only. Early in my teachings of design patterns I thought Adapters and Facades would always come after other noncreational patterns in the order of defining context. In fact, they usually do. However, some systems have the requirement of building to a particular interface. In this case, it is a Facade or an Adapter (just one of many in the system, of course) that may be the seniormost pattern.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.96.188