Development, Marketing and Technical Support teams play a vital role in the success of a software product. Development team is of course the backbone of the product, Marketing team plays a key role in selling the product and Technical support team provides after sales support to the customers. Often, I use to think why development team could not provide support. I found the answer only after a very long time. Technical support team should interact with both customers and computers, whereas the development team spends most of their time in hacking computers. So, the members working in both teams should have different mind set and have totally different roles to play. On top of this, the nature of operation in both the teams is mutually exclusive. For example, we can see products having 24 x 7 technical support, whereas the development team hardly works round the clock. The change in the operational strategy in one of the team will not have a direct impact on the other. In short, the technical support team acts as an interface to the product whereas the development team implements the product.
When I was reading the book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al. (Addison-Wesley, 1995) written by 'Gang of Four (GoF)', I could relate the above-mentioned scenario with the Bridge Pattern. Separating the technical support team from the development team is similar to isolating the abstraction from implementation. That's what the Bridge Pattern is intended to do. In this article, I will be talking about the Bridge Pattern, what, why and when it is needed. Benefits and drawbacks in using the Bridge Pattern are also presented. The discussion will not be complete without mentioning about the variants and known uses of the Bridge Pattern in popular libraries. Let me start the discussion with a simple programming example.
Internet is a very good example for proving the fact, "A picture is better than thousand words". When it all started, HTML was just a text based markup language, concentrating mainly on the structure of the document but not its presentation. However, HTML has gone through various revisions to include graphics and images. Now, thousands of web sites, spread all over the Internet, contains GIF and JPEG images. Though GIF and JPEG are popular image formats used on the web, there are hundreds of other image formats such as BMP, PCX, TIFF, TARGA etc., which serve very different purposes and are popular in different operating systems. For example, BMP image format is widely used on Windows operating systems, however its existence is also there on OS/2, Macintosh and UNIX operating systems.
The structure and the representation are two important aspects of an image format. The structure defines the way in which the image is stored and the representation deals with the display of the image. For a given format, the structure remains the same across operating systems, whereas the representation or the way in which the image is displayed may vary between operating systems. For example, the structure of a Windows BMP file remains unchanged in all operating systems, but the mechanism used by the Windows operating system to display a BMP file is different from the one used by Macintosh or OS/2 operating systems to display the same file. On the other hand, for a given operating system, the representation can remain unchanged across different image formats. For example, Windows can display an image represented as a Bitmap object, without having to know about its source format which can be a BMP, a JPEG or a PCX. In short, the representation and the structure of an image format are two different aspects and they should be allowed to vary independently based on the other factors like operating system, hardware etc.
Bridge Pattern classified under the Structural pattern by 'Gang of Four (GoF)' can be used to abstract and model these variations. According to GoF, the Bridge Pattern is intended to "Decouple an abstraction from its implementation so that the two can vary independently". In this article, I will be using the terms used by GoF to explain the Bridge Pattern.
This article uses an Image viewer application to explain the concept behind the Bridge Pattern. This sample application is designed to view BMP files on Windows operating systems. However, it can easily be extended to view other image formats like JPEG on Windows or view BMP images on other operating systems like OS/2.
This example uses two-class hierarchies viz., CImage and CImageImp (see diagram). CImage class hierarchy defines the abstraction for the clients and CImageImp class hierarchy provides implementation for the specified abstraction. CImage and its derived classes are responsible for handling different image formats such as BMP, JPEG, PCX etc., and CImageImp classes are responsible for the representation of the images on different operating systems like Windows, OS/2. The CImage object provides basic services for loading and showing images and it is configured with a CImageImp object. Services that are dependent on a particular implementation (like show) are forwarded to CImageImp class (say to PaintImage). In this way, new image formats can be added to the CImage class hierarchy without affecting the CImageImp and CImageImp can be extended to provide implementation for a new operating system without affecting CImage. In short, the goal of the Bridge Pattern is achieved, that is, to vary abstraction and implementation independently.
Bridge Pattern has four participants that include Abstraction, Refined Abstraction, Implementor and Concrete Implementor. In this example, the abstract image class CImage, is referred as the Abstraction, the concrete image class CBmpImage (for handling Windows Bitmaps) is referred as Refined Abstraction, the abstract image implementation class CImageImp is referred as Implementor and the concrete class CWinImp that implements the interfaces of the Implementor is referred as the Concrete Implementor. The application using the CImage Abstraction is the client. Depending on the operating system, the client can configure the CImage subclass (Refined Abstraction) with a concrete CImageImp class object (Concrete Implementor).
CImage maintains a reference to the CImageImp object. When the client calls Load or Show method in CImage, it does some preprocessing and forwards the request to CImageImp object by calling InitImageInfo or PaintImage method that provides the actual implementation. Isolating the image and image implementation in separate class hierarchies entitles them to vary independently. UML diagram showing the relationship between the participants of the Bridge Pattern is presented below. Listing 1 contains the class declarations and Listing 2 contains sample method implementations.
Benefits in using Bridge Pattern
- Decoupling abstraction from implementation - Inheritance tightly couples an abstraction with an implementation at compile time. Bridge pattern can be used to avoid the binding between abstraction and implementation and to select the implementation at run time.
- Reduction in the number of sub classes - Sometimes, using pure inheritance will increase the number of sub classes. Let us assume that the full-blown version of our Image Viewer supports 6 image formats in 3 different operating systems. Pure inheritance would have resulted in 18 sub classes whereas applying Bridge Pattern reduces the sub class requirement only to 9.
- Cleaner code and Reduction in executable size - In the above example, operating system specific code is encapsulated in CImageImp sub classes. This results in a cleaner code without much preprocessor statements like #ifdefs, #ifndefs. Also, it is easy to conditionally compile CImageImp sub classes for a given operating system to reduce the size of the executable.
- Interface and implementation can be varied independently - Maintaining two different class hierarchies for interface and implementation entitles to vary one independent of the other.
- Improved Extensibility - Abstraction and implementation can be extended independently. As mentioned earlier, the above example can easily be extended to view other image formats on Windows or view BMP images on other operating systems.
- Loosely coupled client code - Abstraction separates the client code from the implementation. So, the implementation can be changed without affecting the client code and the client code need not be compiled when the implementation changes. (NOTE : In the above mentioned example, for the sake of simplicity, the application configures the CImage object with the right CImageImp object. However, alternate methods like Abstract Factory can be adopted to choose the CImageImp object.)
Drawbacks in using Bridge Pattern
1. Double indirection - In the above example, operating system specific methods are implemented by subclasses of CImageImp class. CImage class must delegate the message to a CImageImp subclass which implements the appropriate method. This will have a slight impact on performance.
Reference counting is a technique to allow multiple objects with the same value to share a single representation of that value. The advantage of sharing representation reduces the memory overhead in case of large objects. A simple example is a String class, in which multiple objects can share the same String representation. String class is referred to as a Handle class and String representation is referred to as a Body class. Handle class specifies the interface and the Body class maintains a reference count and implements the actual representation. Clients interact with the Body class through the interface specified by the Handle class. Handle/Body separation abstracts the client from implementation changes. This structure is very similar to the Bridge Pattern, however the intent is different.
Sometimes, there may be only one Implementor class for a given Abstraction. Therefore, an Abstract Implementor class is not needed. This leads to a one to one relationship between Abstraction and Implementor classes. The separation may still be useful to change the Implementor without affecting any of its clients. GoF refers this as Degenerate Bridge Pattern, where there is a one-to-one relationship between Abstraction and Implementor. GoF explains Degenerate Bridge with an example from libg++. Libg++ defines classes that implement common data structures, such as Set, LinkedSet, HashSet, LinkedList, and HashTable. Set is an abstract class that defines a set abstraction, while LinkedList and HashTable are concrete implementors for a linked list and a hash table, respectively. LinkedSet and HashSet are Set implementors that bridge between Set and their concrete counterparts LinkedList and HashTable.
This section presents known uses of Bridge Pattern. Some of the known uses presented in this section are taken from the GoF book on Design Patterns.
MFC and Bridge Pattern
In MFC, the process of storing/retrieving an object to/from a persistence mechanism (like a file) is called Serialization. MFC uses the Bridge Pattern to implement Serialization. CArchive and CFile classes implement object Serialization. CArchive class provides the interface for writing/reading an object to/from a persistence mechanism whereas the CFile and its sub classes provides implementation for different persistence mechanisms such as memory, disk file, sockets etc.
A CArchive object is configured with an object of class CFile (or a derived class) during its construction, from which it obtains the necessary information for serialization, including the filename and type of the requested operation (a read or write). Client performing the Serialization operation can use CArchive object without regarding the persistence mechanism implemented by CFile classes.
Java and Bridge Pattern
Java uses the Bridge Pattern to separate Components and Component Peers. Java applications can run on different platforms, so the client code should be able to create a Component without committing to a concrete implementation. The Components and Component Peers are represented as two different class/interface hierarchies. Every AWT Component sub class has a corresponding Component Peer sub interface with which it can communicate. Platform specific classes implement these Component Peer interfaces.
Other known uses (from GoF book on Design Patterns)
The ET++ Window/WindowPort design extends the Bridge Pattern in that the WindowPort also keeps a reference back to the Window. The WindowPort implementor class uses this reference to notify Window about WindowPort-specific events : the arrival of input events, window resizes, etc.
NeXT's AppKit uses the Bridge Pattern in the implementation and display of graphic images.
Bridge and Strategy
Often, the Strategy Pattern is confused with the Bridge Pattern. Even though, these two patterns are similar in structure, they are trying to solve two different design problems. Strategy is mainly concerned in encapsulating algorithms, whereas Bridge decouples the abstraction from the implementation, to provide different implementation for the same abstraction.
The article Applying Strategy Pattern in C++ Applications talks about the Strategy Pattern in detail.
Bridge and Adapter
The structure of the Adapter Pattern (object adapter) may look similar to the Bridge Pattern. However, the adapter is meant to change the interface of an existing object and is mainly intended to make unrelated classes work together.
This article not only presented what a Bridge Pattern is but also went into why and when it is really needed. To summarize, pure inheritance hardwires the abstraction and the implementation. Bridge Pattern can be used when an abstraction can have different implementations and when both of them can vary independently.
Special thanks to my friend Sree Meenakshi for her helpful suggestions in improving the clarity and presentation of this article.
Listing 1 - CImage and CImageImp class declarations containing important class members
virtual INT Load( LPCSTR, CRuntimeClass * ) = 0;
virtual INT Show( CWnd *, WPARAM );
CImageImp * m_pImageImp;
class CBmpImage : public CImage
virtual INT Load( LPCSTR, CRuntimeClass * );
class CImageImp : public CObject
virtual INT InitImageInfo( LPSTR ) = 0;
virtual BOOL PaintImage( CWnd *, CRect * ) = 0;
class CWinImp : public CImageImp
INT InitImageInfo( LPSTR );
BOOL PaintImage( CWnd *, CRect * );
BYTE * m_pBmi;
CPalette * m_pPalette;
INT CImage::Show( CWnd * pWnd, WPARAM wParam )
ASSERT( m_pImageImp != NULL );
return m_pImageImp->PaintImage( pWnd, ( CRect * ) wParam );
INT CBmpImage::Load( LPCSTR lpszFileName, CRuntimeClass * pRuntimeClass )
m_pImageImp = ( CImageImp * ) pRuntimeClass->CreateObject();
if( m_pImageImp == NULL )