Click here to Skip to main content
15,867,141 members
Articles / Programming Languages / C++/CLI
Article

Bridge Pattern - Bridging the gap between Interface and Implementation

Rate me:
Please Sign up or sign in to vote.
4.60/5 (26 votes)
8 Jan 2001 197.7K   686   83   8
This article discusses the Bridge Pattern, what it is, why and when it is needed.

Introduction

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.

An 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.

Image 1

Benefits in using Bridge Pattern

  1. 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.

  2. 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.

  3. 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.

  4. Interface and implementation can be varied independently - Maintaining two different class hierarchies for interface and implementation entitles to vary one independent of the other.

  5. 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.

  6. 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.

Variants

Handle/Body

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.

Degenerate Bridge

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.

Known Uses

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.

Summary

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.

Acknowledgments

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

class CImage
{
    // Method declarations
    public :
        virtual INT Load( LPCSTR, CRuntimeClass * )    = 0;
        virtual INT Show( CWnd *, WPARAM );

    // Data members
    protected :
        CImageImp    * m_pImageImp;
};

class CBmpImage : public CImage
{
    // Method declarations
    public :
        virtual INT Load( LPCSTR, CRuntimeClass * );
};

class CImageImp : public CObject
{
    // Method declarations
    public :
        virtual INT    InitImageInfo( LPSTR )           = 0;
        virtual BOOL    PaintImage( CWnd *, CRect * )   = 0;

    // Attributes
    public :
        LPBYTE      m_pImage;
        LONG        m_lNormalWidth;
        LONG        m_lNormalHeight;
};

class CWinImp : public CImageImp
{
    // Method declarations
    public :
        INT        InitImageInfo( LPSTR );
        BOOL       PaintImage( CWnd *, CRect * );

    // Attributes
    protected :
        BYTE        * m_pBmi;
        CPalette    * m_pPalette;
};

Listing 2 - Implementation of Show and Load methods of CImage and CBmpImage classes

INT CImage::Show( CWnd * pWnd, WPARAM wParam )
{
    // Step 1 - Check and delegate this method to m_pImageImp
    ASSERT( m_pImageImp != NULL );
    return m_pImageImp->PaintImage( pWnd, ( CRect * ) wParam );
}

INT CBmpImage::Load( LPCSTR lpszFileName, CRuntimeClass * pRuntimeClass )
{
    // Some initialization code before creating image implementation object
    …
    …
    //  Initialize image information, after creating image implementation object
    m_pImageImp = ( CImageImp * ) pRuntimeClass->CreateObject();
    if( m_pImageImp == NULL )
    {
        …
        …
        return FAILURE;
    }
    m_pImageImp->InitImageInfo(..);
    …
    …
    return SUCCESS;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Switzerland Switzerland
Kulathu Sarma is working as a Technology Manager for GoldAvenue, a company based in Geneva, Switzerland and responsible for supporting e-business initiatives of GoldAvenue in B2B and B2C Sectors. He has been programming in C/C++ for 9 years and actively working on Patterns for the past 5 years.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Rajiv Chauhan24-Dec-11 6:59
Rajiv Chauhan24-Dec-11 6:59 
GeneralMy vote of 5 Pin
zzfima27-Mar-11 2:15
zzfima27-Mar-11 2:15 
GeneralVisual Studio 2010 ... minor alteration et voila, compiles! Pin
RedDk31-Dec-10 8:56
RedDk31-Dec-10 8:56 
GeneralMy vote of 2 Pin
400mike28-Nov-09 10:54
400mike28-Nov-09 10:54 
Not convinced. Explains as everybody else. Use explicit/real world example.
GeneralAbout the benefits Pin
Refky Wahib5-May-07 1:03
Refky Wahib5-May-07 1:03 
GeneralAn example of communication between specific subclasses. Pin
WREY20-Feb-04 12:50
WREY20-Feb-04 12:50 
GeneralExtended Bridge to implement reference counting Pin
Jonathan Gilligan10-Jan-01 8:47
Jonathan Gilligan10-Jan-01 8:47 
GeneralRe: Extended Bridge to implement reference counting Pin
18-Jul-02 21:37
suss18-Jul-02 21:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.