Understanding Bridge Design Pattern made Easy

Bridge pattern is one of the 23 design patterns proposed to solve varieties of issues generally faced when designing robust and extensible software, by using the thumb rules of object oriented programming such as abstraction, encapsulation and inheritance.

Bridge pattern is one of the 23 design patterns proposed to solve varieties of issues generally faced when designing robust and extensible software, by using the thumb rules of object oriented programming such as abstraction, encapsulation and inheritance. The Bridge pattern is a structural pattern which aims at "decoupling an abstraction from its implementation so that the two components can vary independently". In other words, let there be a "Bridge" which shadows an abstraction from its implementation such that any change in the implementation shall not affect the other components related to it. The "Bridge" pattern primarily works on the concepts of Abstraction and Inheritance.

A Builder pattern typically consists of four components:

  1. Abstraction: The core of the builder pattern which defines an abstract interface. This component also holds on to an instance of an Implementor interface and acts as a "bridge" between the variants of the abstraction and its varied implementations.
  2. Refined Abstraction: An Extension of the Abstraction defined above
  3. Implementor: An abstraction for the various implementations available, this type is holded by the Abstraction component for accessing its implementations
  4. Concrete Implementor: Any varied implementation of the Implementor component defined above

Consider the below example on the lines of Abstraction-Implementor.

// An Interface for abstracting the (Abstractor) Bridge
public interface IAbstractor
{
    void CallImplementor();
}

// The Refined Abstractor (Bridge) which takes in Implementor
// And Does the Transformed Calling
public class RefinedAbstractorOne : IAbstractor
{
    private readonly IImplementor _implementor;

    public RefinedAbstractorOne(IImplementor implementor)
    {
        _implementor = implementor;
    }

    public void CallImplementor()
    {
        Console.WriteLine("This is Abstractor One");
        _implementor.DoSomething();
    }
}

// Another implementation of the Abstractor
public class RefinedAbstractorTwo : IAbstractor
{
    private readonly IImplementor _implementor;

    public RefinedAbstractorTwo(IImplementor implementor)
    {
        _implementor = implementor;
    }

    public void CallImplementor()
    {
        Console.WriteLine("This is Abstractor Two");
        _implementor.DoSomething();
    }
}

Now that we have an Abstract class, with two variants of the interface and each class for itself accepts an object of type IImplementor, which is some implementation. And notice the abstraction strictly maintained between the communicating components, which facilitates us with varying the IImplementor with what ever extension of the interface we like.

// The Implementor Interface for Abstraction
// This type is retained by the bridge
public interface IImplementor
{
    void DoSomething();
}


// Some Implementation of the Implementor interface
public class ImplementorOne : IImplementor
{
    public void DoSomething()
    {
        Console.WriteLine("This is Implementor One. does something.");
    }
}

// Some Implementation of the Implementor interface
public class ImplementorTwo : IImplementor
{
    public void DoSomething()
    {
        Console.WriteLine("This is Implementor Two. does something.");
    }
}

And finally the Client class where the instantiation happens is defined as:

// The Client where the Magic happens
public class BuilderClient
{
    public void CallTheBridge()
    {
        IImplementor implementor;
        IAbstractor bridge;

        // instantiate Implementor One
        implementor = new ImplementorOne();
        // fill the Bridge with Implementation
        bridge = new RefinedAbstractorOne(implementor);
        // call on the bridge
        bridge.CallImplementor();

        // vary the bridge
        implementor = new ImplementorTwo();
        bridge = new RefinedAbstractorTwo(implementor);
        bridge.CallImplementor();
    }
}

Observe that for one reference type of implementor and bridge, we can vary the implementations among ImplementorOne and ImplementorTwo for two Abstractions RefinedAbstractorOne and RefinedAbstractorTwo.

The output will be:


This is Abstractor One
This is Implementor One. does something.
This is Abstractor Two
This is Implementor Two. does something.

Similarities with Adapter pattern:

When we look at it, a Bridge generally does the same thing as what an Adapter does; it does convert one abstract type IAbstractor and uses it to call another abstract type IImplementor with all the abstractions and decoupling on. But a Bridge pattern is used in times when both the abstract type and its implementation should be free and decoupled of each other with a scope of each to be extended in their own lines. Theoritically it sounds different, but when it comes to implementation, a Bridge pattern is implemented similarly as how we implement an object-based Adapter pattern.

Points to Note:

  1. Bridge pattern decouples the abstract and implementation components so that each can be developed and extended individually
  2. Since it is a structural pattern, has close similarities with an Adapter pattern
  3. All the dependencies and communication happens via interfaces or abstract types and hence each of the component has scope to vary in its actual implementations
  4. The link or the "bridge" is the instance of the implementor available within the abstractor

Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *