Structural

Bridge (Handle)

Purpose

Decouples an abstraction from its implementation so that both can change independently.

UML

Behavior

An abstraction class forwards client requests to the appropriate implementer object.

Example

The Bridge pattern is used to decouple the interface of a list class, YDList, from its implementation. There are two possible implementations, a thread-unsafe implementation and a thread-safe implementation. Note how in the main program, the client sees  the same interface for both types of implementations.

In determining which implementation to bind, the Factory method could be used to polymorphically bind the appropriate implementation.

Usage

/* Abstraction classes */

/* Abstraction class: Defines interface visible to client. It also maintains a reference to the implementer object. In this example, the abstraction class defines a list interface which can either be implemented as thread safe or thread unsafe. This class defines a minimal interface for illustration purposes. */
template<typename T>
class YDList
{
// Constructors/Destructors
public:
    YDList(const std::string& strMode)
    {
        // Create the appropriate implementation. This could be made much elegant by using a
        // Factory method to polymorphically bind the appropriate implementation.
        if (strMode == "TS")
            m_pImp = new TSList<T>;
        else if (strMode == "TU")
            m_pImp = new TUList<T>;
        else
            throw std::invalid_argument("Unsupported list type");
    }
    virtual ~YDList()
    {
        // The usual checking and error logging should go here.
        delete m_pImp;
    }

// Public interface
public:
    virtual void push_front(const T& elem)
    {
        // Delegate method to the chosen implementation
        m_pImp->push_front( elem );
    }
    virtual void push_back(const T& elem)
    {
        // Delegate method to the chosen implementation
        m_pImp->push_back( elem );
    }
    virtual void pop_front()
    {
        // Delegate method to the chosen implementation
        m_pImp->pop_front();
    }
    virtual void pop_back()
    {
        // Delegate method to the chosen implementation
        m_pImp->pop_back();
    }
protected:
    // protected for possible use by derived classes
    ListImp<T>* m_pImp;
};

/* Implementation Classes*/

/* Implementor: An abstract base class that defines an interface for implementation classes. Note that the implementor interface need not correspond directly to that of the Abstraction class (YDList)*/

template<typename T>
class ListImp
{
public:
    virtual void push_front(const T&) = 0;
    virtual void push_back(const T&) = 0;
    virtual void pop_front() = 0;
    virtual void pop_back() = 0;
};

/* Concrete Implementor: Implements the interface defined by Implementor. In this example, TUList is a thread-unsafe list that simply delegates all calls to its std::list member. TUList is a Wrapper.*/
template<typename T>
class TUList : public ListImp<T>
{
// Public interface
public:
    void push_front(const T& elem)
    {
        m_list.push_front( elem );
    }
    void push_back(const T& elem)
    {
        m_list.push_back( elem );
    }
    void pop_front()
    {
        m_list.pop_front();
    }
    void pop_back()
    {
        m_list.pop_back();
    }

// Data members
private:
    std::list<T> m_list;
};

/* Concrete Implementor.
In this example, TSList is a thread-safe list that serializes all access to its std::list member object.  For simplicity (Ok! I'm being lazy), I haven't explicitly added the serialization code. However, the Scoped Locking pattern provides a very elegant solution (basic idea: on construction lock access, on destruction unlock access) */

template<typename T>
class TSList : public ListImp<T>
{
// Public interface
public:
    void push_front(const T& elem)
    {
        std::cout << "Thread-safe access!" << std::endl;
        // Use Scoped Locking to enter a critical section
        // Add code here ...

        m_list.push_front( elem );

        // Critical section released when scoped locking object goes out of scope at end of function
   
}
    void push_back(const T& elem)
    {
        std::cout << "Thread-safe access!" << std::endl;
        // Use Scoped Locking to enter a critical section
        // Add code here ...

        m_list.push_back( elem );

        // Critical section released when scoped locking object goes out of scope at end of function
    }
    void pop_front()
    {
        std::cout << "Thread-safe access!" << std::endl;
        // Use Scoped Locking to enter a critical section
        // Add code here ...

        m_list.pop_front();

        // Critical section released when scoped locking object goes out of scope at end of function
    }
    void pop_back()
    {
    std::cout << "Thread-safe access!" << std::endl;
        // Use Scoped Locking to enter a critical section
        // Add code here ...

        m_list.pop_back();

        // Critical section released when scoped locking object goes out of scope at end of function
   
}

// Data members
private:
    std::list<T> m_list;
};

int main(int argc, char* argv[])
{
    try
    {
        YDList<int> obList( "TS" );
        obList.push_back( 10 );
        obList.push_back( 20 );
        obList.pop_back();
        obList.pop_back();
        obList.pop_back(); // extra pop_back. Doesn't throw!
    }
    catch( const std::exception & e)
    {
        std::cout << e.what() << std::endl;
    }

    try
    {
        YDList<double> obList( "YD" ); // oops. Unsupported list type. Throws std::invalid_argument
        obList.push_back( 10.0 );
    }
    catch( const std::exception & e)
    {
        std::cout << e.what() << std::endl;
    }
    return 0;
}

Notes