Like most of the design patterns we’ve dealt with both in our book and on this blog we like to start with a minimalist example and then provide a more concrete and useful example. With the Bridge design pattern, the example is fairly minimalist, but it has been designed to create graphic backdrops for video objects. So, while not exactly minimalist and certainly not abstract, the Bridge example here is still fairly simple. (Well, at least as simple as design patterns ever get.) Besides, it accomplished something I needed.
Bridge Over Untroubled Waters
With most design patterns I’ve found that their abstract concepts are clearer than their actual creation. (The Mediator design pattern in this blog is a good example.) However, with the Bridge, I found that the concept was a bit muddled, but making a Bridge design pattern that actually accomplished something useful was relatively simple. To get started, take a look at the class diagram in Figure 1:
Figure 1: Bridge Class Diagram
The intent of the Bridge pattern is to decouple an abstraction from its implementation so that the two can vary independently. (GoG 151) If you don’t think about that much, it sounds like a good idea to keep an application from grinding its gears when a change is made in either the abstraction or implementation. The Freemans (Head First Design Patterns, pp. 612-613) have a great example—a universal remote control. The remote control is the abstraction and a TV set is the implementor. Concrete implementors are the different brands of TVs. As new technology arises, the remote control can be updated with new gizmos. Likewise, the TV sets can also be updated and different brands will have their own unique features. A good Bridge design will allow each to be changed without breaking the other. So far so good.
An Abstraction Has-A Implementor
As I started looking at different Bridge design pattern examples I ran into examples that were examples of something but did not seem to be Bridge patterns. (One was nothing more than a single interface with two different implementations. The author explained, See, that’s a bridge. I sat there with question marks floating above my head like some cartoon character.) The problem was that I was still unsure of exactly how everything was supposed to work together. The GoF intent statement and Freeman example were quite clear. However, the Abstraction interface and the Implementor interface, either of which could be an abstract class or interface structure, were bridged by the abstraction having-a implementor. That threw me, and since the design pattern is a Bridge, I really had to understand the relationship known as a bridge between the Abstraction and Implementor participants.
Initially, I was looking for a structure in the interface itself that actually had an implementor in its signature, but after looking more closely, I settled for an implied aggregation. For example, in the sample application an operation laid out in the Abstraction interface will include an Implementor object when that interface is implemented by the RefinedAbstraction. Consider the following from the sample:
IShape ( Abstraction Interface) includes an operation doDraw(). The details of the doDraw() operation in the RefinedAbstraction, which is the implementation of the IShape interface, includes an IBuildBack object (Implementation Interface) in the constructor parameters. The doDraw() method includes:
buildBack.drawBack(xPos,yPos,wide,high);
The buildBack object is typed as IBuildBack, the interface of the Implementor class. So, the Implementor is implied in the doDraw() method; thus the Abstraction participant has- a Implementor.
The Abstraction class (IShape) aggregates the Implementor implicitly.
A Refined Abstraction
Nowhere else in the GoF book is there mention of a refined abstraction outside of the Bridge pattern. Generally, once an interface has abstracted methods, the job of those classes that implement the interface is to provide concrete operations. However, the refined abstraction extends the interface defined by the Abstraction (GoF, 154). So a refined abstraction is just an extension of another Interface (or Abstract class). Therein lies the final answer to the question about the implied aggregation. As you saw in the previous section, the Bridge design pattern is named after the bridge held up by aggregation between the Abstraction interface and the Implementor interface. However, we may not see that aggregation until the abstraction is refined in implementation by another class. The concrete implementation is done by the implementation of the Implementor class.
At this point, the arrangement of the pattern clears up. The abstraction is separated from the implementation by refining the abstraction rather than implementing it by providing a concrete class. The implementation interface is implemented with concrete classes. However, the abstract class is the entry way for the client, which has-a implementation.
Here Comes the Client
In case you were wondering what the faded gray box with “Client” is for, we have to go to the appendix of the GoF book (pp 364-365)—it indicates that the client is not a participant in the class but is useful to show which participants interact with client. In this case the client is a collaborator, and it interacts with the abstraction which forwards the client’s request to the implementor object.
The significant point here is that the client interacts with an abstract object, both the interface and the refined abstraction of the interface. Intuitively, we might assume it interacts directly with the implementor object, but the little gray box shows us otherwise. That’s why these diagrams can be so useful even though at times I think that a mad (computer) scientist concocted them to thwart understanding.
Making Backdrops: A Bridge Design Pattern Example
As noted at the beginning of this article, I wanted to use the Bridge pattern to create backdrops for videos. These backdrops are nothing more than code-generated Shape objects (rectangles) that can be cooked up with ActionScript. I discovered that the backdrops had a few variations including size, frame width, and both frame and fill color. Starting with a Java example I had found for creating circles, I reworked it to deal with rectangles. In working with the transformation both from Java to ActionScript and from circles to rectangles, I kept making changes and therein discovered how flexible the Bridge design pattern actually is. Also, I found some nice features in Java that make graphic work fairly easy. The result was that getting a trace abstract version of a Bridge was pretty simple. The problem that I ran into (that Java did not seem to have) was working with graphics going through the Abstract interface. Having worked with several different design patterns, getting graphics to behave when the relationship between the graphic generating method and the class to put it on the stage can be strained. At some point I decided that rather than trying to place the graphic operations in the concrete Implementor classes, I’d have the concrete Implementors generate the information and then the client would instantiate objects from a separate class to handle the graphics. (When one class instantiates objects from another class, the dashed line is used with the arrow pointing to the insantiatee.) The class does nothing more than provide the necessary imports and code for creating rectangles. This utility class was not a Bridge pattern participant, as shown in Figure 2 but what you might call a friend of a colleague.
Figure 2: Bridge Class Diagram for Making Backdrops
The Abstraction Classes
Whenever possible I prefer to use an interface instead of an abstract class because ActionScript 3.0 has no proper abstract class, per se. In our book, Chandima and I have a few rants about this and eventually blame it on the perversity of ECMAScript standards. However, creating classes that act like abstract classes is easy enough in ActionScript 3.0; so in cases where abstract classes are absolutely needed they are employed. For more information about ActionScript 3.0 and ECMA standards see this article Chandima found,
http://blogs.adobe.com/open/2008/08/blog_entry_dated_81408_715_pm.html
The IShape interface provides the operational abstraction the client needs to make a request that is passed on to the implementor classes. Only methods are defined, one for drawing and one for changing the shape size.
1 2 3 4 5 6 7 8 9 | //Abstraction package { public interface IShape { function doDraw():Array;// low-level function superSize(size:Number):void;// high-level } } |
Next, you can see the refined abstraction that creates the aggregation—the bridge—between the abstraction and the implementor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //Refined Abstraction package { public class BackDrop implements IShape { private var xPos:Number; private var yPos:Number; private var wide:Number; private var high:Number; private var buildBack:IBuildBack; private var bb:Array; public function BackDrop(xPos:Number,yPos:Number,wide:Number,high:Number,buildBack:IBuildBack) { this.xPos=xPos; this.yPos=yPos; this.wide=wide; this.high=high; this.buildBack=buildBack; } // Implementation specific -- you can change the implementation public function doDraw():Array { bb=buildBack.drawBack(xPos,yPos,wide,high); return bb; } // Abstraction specific -- you can change the Asbstration public function superSize(size:Number):void { wide*=size; high*=size; } } } |
The doDraw() method returns an array created by an implementor object, buildBack typed as the IBuildBack interface. The reason that the method is set up to return array data generated by the implementor is because I could not find a way to generate the necessary graphics in the implementor and pull them through the abstraction and get them on the stage when the client made a request. (Anyone who has a way to do that is encouraged to offer suggestions.)
The Implementor Classes
The implementor has a single method, drawBack(). The operation provides parameters for specifying the position and size of a rectangle.
1 2 3 4 5 6 7 8 9 | //Implementor package { public interface IBuildBack { function drawBack(xPos:Number, yPos:Number, wide:Number, high:Number):Array; } } |
Next, two different classes implement the abstract method. All of the necessary data are placed into an array. Using both the parameter list and adding literals for the stroke and fill colors and stroke size of the rectangle, everything could be packaged for eventually drawing the requested rectangle. The following two classes implement the IBuildBack interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //ConcreteImplementor #1 package { public class BuildBack1 implements IBuildBack { private var b1:Array; public function drawBack(xPos:Number,yPos:Number,wide:Number,high:Number):Array { b1=new Array(xPos,yPos,wide,high,0xcc0000,5,0x00cc00); return b1; } } } //ConcreteImplementor #2 package { public class BuildBack2 implements IBuildBack { private var b2:Array; public function drawBack(xPos:Number, yPos:Number, wide:Number, high:Number):Array { b2=new Array(xPos,yPos,wide,high,0x0000cc,1,0xcc0000); return b2; } } } |
The literals placed in the arrays (b1 and b2) insures that each class provides the unique colors and frame size of the backdrops. All the client needs to request is the size and position on the stage.
The Client Collaborator
The client classes are made up of a requesting class that specifies what it wants from the Bridge and a utility class that crunches out rectangles (something like a rectangle factory.) The client request is made through the abstraction interface and refined abstraction. From those sources, the values are brought into the doDraw() method and made available for the utility class that makes the rectangles, MakeBackDrop. The following code listings show both classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | //Client Collaborator package { import flash.display.Sprite; public class BridgeBuilder extends Sprite { private var shape1:IShape; private var shape2:IShape; private var builder:Array; private var makeBackDrop1:MakeBackDrop; private var makeBackDrop2:MakeBackDrop; public function BridgeBuilder() { shape1=new BackDrop(100,50,160,120,new BuildBack1()); shape2=new BackDrop(100,200,160,120,new BuildBack2()); shape1.doDraw(); builder=shape1.doDraw(); makeBackDrop1=new MakeBackDrop(); makeBackDrop1.makeVidBack(builder[0],builder[1], builder[2], builder[3],builder[4], builder[5], builder[6]); addChild(makeBackDrop1); shape2.superSize(1.5); builder=shape2.doDraw(); makeBackDrop2=new MakeBackDrop(); makeBackDrop2.makeVidBack(builder[0],builder[1], builder[2], builder[3],builder[4], builder[5], builder[6]); addChild(makeBackDrop2); } } } //Utility class package { import flash.display.Sprite; import flash.display.Graphics; import flash.display.Shape; public class MakeBackDrop extends Sprite { private var vidBack:Shape; public function makeVidBack(xPos:Number,yPos:Number,wide:Number,high:Number,cFill:Number,lSize:Number,cLine:Number) { vidBack=new Shape(); vidBack.graphics.beginFill(cFill); vidBack.graphics.lineStyle(lSize,cLine); vidBack.graphics.drawRect(xPos,yPos,wide,high); vidBack.graphics.endFill(); addChild(vidBack); } } } |
Once you’re finished, give it a test run. All you’ll see are two rectangles, each with a 4:3 ratio. They are based on the default 160 x 120 video size if you publish a video stream. In Figure 3, the top image is this default size and the bottom image is “super-sized” by 1.5 to generate 200 x 180 backdrop.
Warming Up to a Unique Pattern
The more I’ve done with the Bridge design pattern, the more I like it. Even when I first got a working version, I found that changing one part or the other was less likely to lead to catastrophic failure than some other patterns. It seems like a good example of loosely coupled classes.
Now I realize that pumping out two rectangles is no great shakes—sort of like one of those T-shirts,
I went all the way to the Bridge design pattern and all I got was two crummy rectangles.
However, I really like the idea that I can make changes in lots of places without tipping over the client class. Also, I like the idea of using a utility class to generate the rectangles instead of what I originally tried in creating the same thing entirely in the concrete implementor classes.
As for the refined abstraction, that’s just plain cool. The interface methods are linked up to an abstract reference to the implementor without one ounce of concrete content. The only thing I can think of is that an abstraction implements an abstraction. If you roll that around in your head you can see possible scenarios with this class—two different refined abstractions with different flavors of implementors.
We welcome your comments, rants, complaints and insights!




Thanks!!
Hi El Mau,
You are very welcome.
Bill
hey there,
when i develop in actionscript i usaully do as the followning;
imagine a game (or watever) if i have to implement some player object, I will certainly create some Player class which would be i kind of structure like a model containing all the data concerning this player… then i would develop a PlayerGraphics which would “listen” to the Player object changes throught addEventListener function….
If i understand well this pattern I should do another one let s call it PlayerBridge that takes the Player & PlayerGraphics in parameter to listen the player and call the playerGraphics functions…is that it? (or maybe i have it completly misunderstood)..