C# Design Patterns: The Bridge Pattern
At first sight, the Bridge pattern looks a lot like the Adapter pattern in that a class is used to convert one kind of interface to another. However, the intent of the Adapter pattern is to make one or more classes' interfaces look the same as that of a particular class. The Bridge pattern is designed to separate a class's interface from its implementation so you can vary or replace the implementation without changing the client code.
The participants in the Bridge pattern are the Abstraction, which defines the class's interface; the Refined Abstraction, which extends and implements that interface; the Implementor, which defines the interface for the implementation classes; and the ConcreteImplementors, which are the implementation classes.
Suppose we have a program that displays a list of products in a window. The simplest interface for that display is a simple ListBox. But once a significant number of products have been sold, we may want to display the products in a table along with their sales figures.
Since we have just discussed the Adapter pattern, you might think immediately of the class-based Adapter, where we adapt the interface of the ListBox to our simpler needs in this display. In simple programs, this will work fine, but as we'll see, there are limits to that approach.
Let's further suppose that we need to produce two kinds of displays from our product data: a customer view that is just the list of products we've mentioned and an executive view that also shows the number of units shipped. We'll display the product list in an ordinary ListBox and the executive view in a DataGrid table display. These two displays are the implementations of the display classes, as shown in Figure 15-1.
Figure 15-1. Two displays of the same information using a Bridge pattern
The Bridger Interface
Now we want to define a single interface that remains the same regardless of the type and complexity of the actual implementation classes. We'll start by defining a Bridger interface.
//Bridge interface to display list classes public interface Bridger { void addData(ArrayList col); }
This class just receives an ArrayList of data and passes it on to the display classes.
We also define a Product class that holds the names and quantities and parses the input string from the data file.
public class Product : IComparable { private string quantity; private string name; //----- public Product(string line) { int i = line.IndexOf ("--"); name =line.Substring (0, i).Trim (); quantity = line.Substring (i+2).Trim (); } //----- public string getQuantity() { return quantity; } //----- public string getName() { return name; } }
On the other side of the bridge are the implementation classes, which usually have a more elaborate and somewhat lower-level interface. Here we'll have them add the data lines to the display one at a time.
public interface VisList { //add a line to the display void addLine(Product p); //remove a line from the display void removeLine(int num); }
The bridge between the interface on the left and the implementation on the right is the ListBridge class, which instantiates one or the other of the list display classes. Note that it implements the Bridger interface for use of the application program.
public class ListBridge : Bridger { protected VisList vis; //------ public ListBridge(VisList v) { vis = v; } //----- public virtual void addData(ArrayList ar) { for(int i=0; i< ar.Count ; i++) { Product p = (Product)ar[i]; vis.addLine (p); } } }
Note that we make the VisList variable protected and the addData method virtual so we can extend the class later. At the top programming level, we just create instances of a table and a list using the ListBridge class.
private void init() { products = new ArrayList (); readFile(products); //read in the data file //create the product list prodList = new ProductList(lsProd); //Bridge to product VisList Bridger lbr = new ListBridge (prodList); //put the data into the product list lbr.addData (products); //create the grid VisList gridList = new GridList(grdProd); //Bridge to the grid list Bridger gbr = new ListBridge (gridList); //put the data into the grid display gbr.addData (products); }