Design Patterns - Bridge

20 April 2022 at 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we had discussed the Adapter design pattern, which falls under the category of structural design patterns. Today, we will explore another design pattern from the same category called Bridge Design Pattern.

Table of contents

  1. Bridge Design Pattern
  2. Implementation of Bridge Design Pattern
  3. Conclusion

Bridge Design Pattern

Bridge design pattern falls under the structural design pattern category. It acts as a connector between two components and helps in communication. Both Adapter and Bridge have very similar working. They mainly differ in their end goal.

The adapter pattern is used when two incompatible components want to communicate and exchange information. Along with that, it can add/modify information before it is sent back to the Client. On the other hand, the Bridge pattern is only used to establish multiple communications between components.

Bridge patterns help to establish many-many mappings by providing multiple ways to implement a function where each path can serve its purpose. In other words, it can be implemented when there is more than one way to complete an activity—for example, saving different file formats based on scenarios (which will be decided during the runtime).

Implementation of Bridge Design Pattern

The bridge design pattern consists of the following components.

  • Abstract class
  • Abstraction details
  • Bridge Abstractio
  • Bridge Implementation
using System;

namespace PARTECH_Bridge
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Bridge implementationA = new ConcreteImplementationA();

            Bridge implementationB = new ConcreteImplementationB(); 

            var refinedAbstraction = new RefinedAbstraction();

            refinedAbstraction.Implementor = implementationA;

            refinedAbstraction.Operation();

            Console.ReadLine();
        }
    }

    public interface Bridge
    {
        public void OpearationImplementation();
    }

    public abstract class Abstraction
    {
        public Bridge Implementor { get; set; }

        public abstract void Operation();
    }

    public class RefinedAbstraction : Abstraction
    {
        public override void Operation()
        {
            Console.WriteLine("RefinedAbstraction: Operation");
            Implementor.OpearationImplementation();
        }
    }

    public class ConcreteImplementationA : Bridge
    {
        public void OpearationImplementation()
        {
            Console.WriteLine("ConcreteImplementationA: OpearationImplementation");
        }
    }

    public class ConcreteImplementationB : Bridge
    {
        public void OpearationImplementation()
        {
            Console.WriteLine("ConcreteImplementationB: OpearationImplementation");
        }
    }

}

Above is the template for implementing the Bridge design pattern. It has:

  • An Abstract class (Abstraction) and an abstract method that needs to be implemented by the derived class. It also contains the reference of the bridge object.
  • Abstraction details (RefinedAbstraction) which is the concrete class that inherits the abstract class and has the implementation of the abstract method.
  • The bridge (Interface - bridge) that acts as a connection between two components.
  • Bridge Implementation(ConcreteImplementationA, ConcreteImplementationB)

And in the main method, an object for the Abstraction details class is created. An object of the concrete implementation is also created, which is then assigned to the implementer of the abstraction details class.

Let us understand this with a real-time scenario. Consider an application that helps to handle multiple air conditioners' remotes. So, every time a new AC is added, a new remote also gets added. There are possibilities where some new features might be added to the AC, but the same remote would still be a perfect fit for it (Modification in one component without impacting the other) and vice versa. Let's now see the code for it.

using System;

namespace PARTECH_Bridge
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IAirConditioner brandAAC = new BrandAAC();

            var brandARemote = new BrandARemote(brandAAC);
            brandARemote.SwitchOn();
            brandARemote.SwitchOff();
            brandARemote.SetTemperature(20);


            IAirConditioner brandBAC = new BrandBAC();
            var brandBRemote = new BrandBRemote(brandBAC);
            brandBRemote.SwitchOn();
            brandBRemote.SwitchOff();
            brandBRemote.SetTemperature(22);

            Console.ReadLine();
        }
    }
    
    /// <summary>
    /// Bridge \ Implementor
    /// </summary>
    public interface IAirConditioner
    {
        void SwitchOn();
        void SwitchOff();
        void SetTemperature(int temperature);
    }
    
    /// <summary>
    /// Abstract Class
    /// </summary>
    public abstract class AbstractRemoteControl
    {
        protected IAirConditioner airConditioner;

        public AbstractRemoteControl(IAirConditioner _airConditioner)
        {
            airConditioner = _airConditioner;
        }

        public abstract void SwitchOn();
        public abstract void SwitchOff();
        public abstract void SetTemperature(int temperature);
    }

    /// <summary>
    /// Abstract definition
    /// </summary>
    public class BrandARemote : AbstractRemoteControl
    {
        public BrandARemote(IAirConditioner airConditioner) : base (airConditioner)
        {

        }

        public override void SwitchOn()
        {
            airConditioner.SwitchOn();
        }

        public override void SwitchOff()
        {
            airConditioner.SwitchOff();
        }
        public override void SetTemperature(int temperature)
        {
            airConditioner.SetTemperature(temperature);
        }
    }

    /// <summary>
    /// Abstract definition
    /// </summary>
    public class BrandBRemote : AbstractRemoteControl
    {
        public BrandBRemote(IAirConditioner airConditioner) : base(airConditioner)
        {

        }

        public override void SwitchOn()
        {
            airConditioner.SwitchOn();
        }

        public override void SwitchOff()
        {
            airConditioner.SwitchOff();
        }
        public override void SetTemperature(int temperature)
        {
            airConditioner.SetTemperature(temperature);
        }
    }

    /// <summary>
    /// Concrete Implementation
    /// </summary>
    public class BrandAAC : IAirConditioner
    {
        public void SwitchOn()
        {
            Console.WriteLine("Brand A AC turned ON.");
        }
        public void SwitchOff()
        {
            Console.WriteLine("Brand A AC turned OFF.");
        }
        public void SetTemperature(int temperature)
        {
            Console.WriteLine($"Brand A AC temperature - { temperature }");
        }
    }
	
    /// <summary>
    /// Concrete Implementation
    /// </summary>
    public class BrandBAC : IAirConditioner
    {
        public void SwitchOn()
        {
            Console.WriteLine("Brand B AC turned ON.");
        }
        public void SwitchOff()
        {
            Console.WriteLine("Brand B AC turned OFF.");
        }
        public void SetTemperature(int temperature)
        {
            Console.WriteLine($"Brand B AC temperature - { temperature }");
        }
    }

}

In the above example,

IAirConditioner is the bridge, AbstractRemoteControl is the Abstract class, and BrandARemote and BrandBRemote are the Refined Abstract class. BrandAAC and BrandBAC are the concrete implementations of the bridge.

Here, we are having a constructor in the RefinedAbstract class, which takes up the bridge as input. And in the main method, both the ACs and remotes are consumed.

The bridge here has three methods for turning on, turning off, and setting the temperature. Similarly, the abstract class has the same set of methods as the constructor. In the Refined Abstract Classes, the methods of the abstract class are overridden, and the respective methods of the bridge are being internally called. For easy understanding, we have hardcoded a few console logging statements.

Once we run the application, the below output appears -

Now, when we need to make changes to AC A or AC B, they are independent of Remote A and Remote B.

Conclusion

And that’s what a Bridge Structural design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.

Latest