Learning the Bridge Pattern: Deriving It

Now that you have been through the problem, we are in a position to derive the Bridge pattern together. Doing the work to derive the pattern will help you to understand more deeply what this complex and powerful pattern does.

Let's apply some of the basic strategies for good object-oriented design and see how they help to develop a solution that is very much like the Bridge pattern. To do this, I will be using the work of Jim Coplien[4] on commonality and variability analysis.

[4] Coplein, J., Multi-Paradigm Design for C++. Reading, Mass.: Addison-Wesley, 1998.

Design patterns are solutions that occur again and again.

Design patterns are solutions that have recurred in several problems and have therefore proven themselves over time to be good solutions. The approach I am taking in this book is to derive the pattern in order to teach it so that you can understand its characteristics.

In this case, I know the pattern I want to derive—the Bridge pattern—because I was shown it by the Gang of Four and have seen how it works in my own problem domains. It is important to note that patterns are not really derived. By definition, they must be recurring—having been demonstrated in at least three independent cases—to be considered patterns. What I mean by “derive” is that we will go through a design process where you create the pattern as if you did not know it. This is to illustrate some key principles and useful strategies.


Coplien's work on commonality/variability analysis tells us how to find variations in the problem domain and identify what is common across the domain. Identify where things vary (commonality analysis) and then identify how they vary (variability analysis).

According to Coplien, “Commonality analysis is the search for common elements that helps us understand how family members are the same.”[5] Thus, the process of finding out how things are common defines the family in which these elements belong (and hence, where things vary).

[5] ibid, p. 63.

Variability analysis reveals how family members vary. Variability only makes sense within a given commonality.

Commonality analysis seeks structure that is unlikely to change over time, while variability analysis captures structure that is likely to change. Variability analysis makes sense only in terms of the context defined by the associated commonality analysis … From an architectural perspective, commonality analysis gives the architecture its longevity; variability analysis drives its fitness for use.[6]

[6] ibid, pp. 60, 64.

In other words, if variations are the specific concrete cases in the domain, commonality defines the concepts in the domain that tie them together. The common concepts will be represented by abstract classes. The variations found by variability analysis will be implemented by the concrete classes (that is, classes derived from the abstract class with specific implementations).

It is almost axiomatic with object-oriented design methods that the designer is supposed to look in the problem domain, identify the nouns present, and create objects representing them. Then, the designer finds the verbs relating to those nouns (that is, their actions) and implement them by adding methods to the objects. This process of focusing on nouns and verbs typically leads to larger class hierarchies than we might want. I suggest that using commonality/variability analysis as a primary tool in creating objects is a better approach than looking at just nouns and verbs (actually, I believe this is a restatement of Jim Coplien's work).

There are two basic strategies to follow in creating designs to deal with the variations:

  • Find what varies and encapsulate it.

  • Favor composition over inheritance.

In the past, developers often relied on extensive inheritance trees to coordinate these variations. However, the second strategy says to try composition when possible. The intent of this is to be able to contain the variations in independent classes, thereby allowing for future variations without affecting the code. One way to do this is to have each variation contained in its own abstract class and then see how the abstract classes relate to each other.

Reviewing encapsulation.

Most object-oriented developers learned that “encapsulation” is data-hiding. Unfortunately, this is a very limiting definition. True, encapsulation does hide data, but it can be used in many other ways. If you look back at Figure 7-2, you will see encapsulation operates at many levels. Of course, it works at hiding data for each of the particular Shapes. However, notice that the Client object is not aware of the particular kinds of shapes. That is, the Client object has no idea that the Shapes it is dealing with are Rectangles and Circles. Thus, the concrete classes that Client deals with are hidden (or encapsulated) from Client. This is the kind of encapsulation that the Gang of Four is talking about when they say, “find what varies and encapsulate it”. They are finding what varies, and encapsulating it “behind” an abstract class (see Chapter 8, “Expanding Our Horizons”).


Follow this process for the rectangle drawing problem.

First, identify what it is that is varying. In this case, it is different types of Shapes and different types of drawing programs. The common concepts are therefore shapes and drawing programs. I represent this in Figure 9-9 (note that the class names are shown in italics because the classes are abstract).

Figure 9-9. What is varying.


At this point, I mean for Shape to encapsulate the concept of the types of shapes that I have. Shapes are responsible for knowing how to draw themselves. Drawing objects, on the other hand, are responsible for drawing lines and circles. I represent these responsibilities by defining methods in the classes.

The next step is to represent the specific variations that are present. For Shape, I have rectangles and circles. For drawing programs, I will have a program that is based on DP1 (V1Drawing) and one based on DP2 (V2Drawing), respectively. I show this in Figure 9-10.

Figure 9-10. Represent the variations.


At this point, the diagram is simply notional. I know that V1Drawing will use DP1 and V2Drawing will use DP2 but I have not said how. I have simply captured the concepts of the problem domain (shapes and drawing programs) and have shown the variations that are present.

Given these two sets of classes, I need to ask how they will relate to one another. I do not want to come up with a new set of classes based on an inheritance tree because I know what happens if I do that (look at Figures 9-3 and 9-7 to refresh your memory). Instead, I want to see if I can relate these classes by having one use the other (that is, follow the mandate to favor composition over inheritance). The question is, which class uses the other?

Consider these two possibilities: either Shape uses the Drawing programs or the Drawing programs use Shape.

Consider the latter case first. If drawing programs could draw shapes directly, then they would have to know some things about shapes in general: what they are, what they look like. But this violates a fundamental principle of objects: an object should only be responsible for itself.

It also violates encapsulation. Drawing objects would have to know specific information about the Shapes (that is, the kind of Shape) in order to draw them. The objects are not really responsible for their own behaviors.

Now, consider the first case. What if I have Shapes use Drawing objects to draw themselves? Shapes wouldn't need to know what type of Drawing object it used since I could have Shapes refer to the Drawing class. Shapes also would be responsible for controlling the drawing.

This looks better to me. Figure 9-11 shows this solution.

Figure 9-11. Tie the classes together.


In this design, Shape uses Drawing to manifest its behavior. I left out the details of V1Drawing using the DP1 program and V2Drawing using the DP2 program. In Figure 9-12, I add this as well as the protected methods drawLine and drawCircle (in Shape), which calls Drawing's drawLine, and drawCircle, respectively.

Figure 9-12. Expanding the design.


One rule, one place.

A very important implementation strategy to follow is to have only one place where you implement a rule. In other words, if you have a rule how to do things, only implement that once. This typically results in code with a greater number of smaller methods. The extra cost is minimal, but it eliminates duplication and often prevents many future problems. Duplication is bad not only because of the extra work in typing things multiple times, but because of the likelihood of something changing in the future and then forgetting to change it in all of the required places.

While the draw method or Rectangle could directly call the drawLine method of whatever Drawing object the Shape has, I can improve the code by continuing to follow the one rule, one place strategy and have a drawLine method in Shape that calls the drawLine method of its Drawing object.

I am not a purist (at least not in most things), but if there is one place where I think it is important to always follow a rule, it is here. In the example below, I have a drawLine method in Shape because that describes my rule of drawing a line with Drawing. I do the same with drawCircle for circles. By following this strategy, I prepare myself for other derived objects that might need to draw lines and circles.

Where did the one rule, one place strategy come from? While many have documented it, it has been in the folklore of object-oriented designers for a long time. It represents a best practice of designers. Most recently, Kent Beck called this the “once and only once rule.”[4]

He defines it as part of his constraints:

  • The system (code and tests together) must communicate everything you want to communicate.

  • The system must contain no duplicate code. (1 and 2 together constitute the Once and Only Once rule).


[4] Beck, K., Extreme Programming Explained: Embrace Change, Reading, Mass.: Addison Wesley, 2000, pp. 108–109.

Figure 9-13 illustrates the separation of the Shape abstraction from the Drawing implementation.

Figure 9-13. Class diagram illustrating separation of abstraction and implementation.


From a method point of view, this looks fairly similar to the inheritance-based implementation (such as shown in Figure 9-3). The biggest difference is that the methods are now located in different objects.

I said at the beginning of this chapter that my confusion over the Bridge pattern was due to my misunderstanding of the term “implementation.” I thought that implementation referred to how I implemented a particular abstraction.

The Bridge pattern let me see that viewing the implementation as something outside of my objects, something that is used by the objects, gives me much greater freedom by hiding the variations in implementation from my calling program. By designing my objects this way, I also noticed how I was containing variations in separate class hierarchies. The hierarchy on the left side of Figure 9-13 contains the variations in my abstractions. The hierarchy on the right side of Figure 9-13 contains the variations in how I will implement those abstractions. This is consistent with the new paradigm for creating objects (using commonality/variability analysis) that I mentioned earlier.

It is easiest to visualize this when you remember that there are only three objects to deal with at any one time, even though there are several classes (see Figure 9-14).

Figure 9-14. There are only three objects at a time.


A reasonably complete code example is shown in Example 9-3 for Java and in the Examples beginning on page 157 for C++.

Example 9-3. Java Code Fragments
class Client {
  public static void main
    (String argv[]) {
      Shape r1, r2;
      Drawing dp;

      dp= new V1Drawing();
      r1= new Rectangle(dp,1,1,2,2);

      dp= new V2Drawing ();
      r2= new Circle(dp,2,2,3);

      r1.draw();
      r2.draw();
   }
}

abstract class Shape {
  abstract public draw() ;
  private Drawing _dp;

  Shape (Drawing dp) {
    _dp= dp;
  }
  protected void drawLine (
    double x1,double y1,
    double x2,double y2) {
      _dp.drawLine(x1,y1,x2,y2);
  }

  protected void drawCircle (
    double x,double y,double r) {
      _dp.drawCircle(x,y,r);
  }
}

abstract class Drawing {
  abstract public void drawLine (
    double x1, double y1,
    double x2, double y2);
  abstract public void drawCircle (
    double x,double y,double r);
}

class V1Drawing extends Drawing {
  public void drawLine (
    double x1,double y1,
    double x2,double y2) {
    DP1.draw_a_line(x1,y1,x2,y2);
  }
  public void drawCircle (
    double x,double y,double r) {
    DP1.draw_a_circle(x,y,r);
  }
}

class V2Drawing extends Drawing {
  public void drawLine (
    double x1,double y1,
    double x2,double y2) {
    // arguments are different in DP2
    // and must be rearranged
    DP2.drawline(x1,x2,y1,y2);
  }
  public void drawCircle (
    double x, double y,double r) {
    DP2.drawcircle(x,y,r);
  }
}

class Rectangle extends Shape {
  private double _x1, _x2, _y1, _y2;
  public Rectangle (
    Drawing dp,
    double x1,double y1,
    double x2,double y2) {
      super( dp) ;
      _x1= x1; _x2= x2 ;
      _y1= y1; _y2= y2;
  }

  public void draw () {
   drawLine(_x1,_y1,_x2,_y1);
   drawLine(_x2,_y1,_x2,_y2);
   drawLine(_x2,_y2,_x1,_y2);
   drawLine(_x1,_y2,_x1,_y1);
  }
}

class Circle extends Shape {
  private double _x, _y, _r;
  public Circle (
    Drawing dp,
    double x,double y,double r) {
      super( dp) ;
      _x= x; _y= y; _r= r ;
  }

  public void draw () {
   drawCircle(_x,_y,_r);
  }
}

// We've been given the implementations for DP1 and DP2

class DP1 {
  static public void draw_a_line (
    double x1,double y1,
    double x2,double y2) {
      // implementation
  }
  static public void draw_a_circle(
    double x,double y,double r) {
      // implementation
  }
}

class DP2 {
  static public void drawline (
    double x1,double x2,
    double y1,double y2) {
      // implementation
  }
  static public void drawcircle (
    double x,double y,double r) {
      // implementation
   }
}

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

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