Bridge Design Pattern in C#

Bridge Design Pattern in C# with Examples

In this article, I will discuss the Bridge Design Pattern in C# with Examples. Please read our previous article discussing the Decorator Design Pattern in C# with Examples. The Bridge Design Pattern falls under the category of Structural Design Pattern. As part of this article, we will discuss the following pointers.

  1. What is the Bridge Design Pattern in C#?
  2. Understanding Abstraction and Implementation in Detail.
  3. Understanding the Bridge Design Pattern in C# with Real-Time Examples.
  4. Implementation of the Bridge Design Pattern in C#.
  5. Understanding the Class Diagram of the Bridge Design Pattern.
  6. Advantages of Bridge Design Pattern
  7. When to use the Bridge Design Pattern in Real-Time Applications?
What is the Bridge Design Pattern in C#?

As per the Gang of Four definitions, the Bridge Design Pattern Decouples an abstraction from its implementation so that the two can vary independently. This pattern involves an interface that acts as a bridge between the abstraction class and implementer classes. It is useful in scenarios where an abstraction can have several implementations, and you want to separate the implementation details from the abstraction.

In the Bridge Design Pattern, there are 2 parts. The first part is the Abstraction, and the second part is the Implementation. The Bridge Design Pattern allows both Abstraction and Implementation to be developed independently, and the client code can only access the Abstraction part without being concerned about the Implementation part.

Understanding the Definition of Bridge Design Pattern:

To understand the definition of a Bridge Design Pattern, please look at the following image. Suppose we have one requirement to SAVE or DELETE an object in the persistence. Here, we can save the object either into a File System or into a Database. In the same way, we can also delete an object either from a File System or from a Database. So, on the right-hand side, you can see two implementers. The FileSystemPersistenceImplementor is used to save the object into a file, whereas the DatabasePersistenceImplementor is used to save the object into a database. In the middle, you can see the Abstraction Layer, which provides two methods to do the Save and Delete Operations. Now, the client will call the Abstraction Layer method, and the abstraction layer will use one of the Persistence implementations to do the operation. So, here, the client is not worried about the Implementation.

What is Bridge Design Pattern in C#?

So, as per the Bridge Design Pattern, the Abstraction and Implementation should be in separate layers. Persistence is the Abstraction Layer, and Persistence Implementation is the Implementation Layer. Now, if you want to add a new implementation or if you want to remove any of the existing implementations, then it will not affect the Abstraction Layer. This is the advantage of the Bridge Design Pattern.

The Abstraction Persistence Layer will use any of the Implementers to SAVE or DELETE an object, and the client will only use the Abstraction Layer to SAVE or DELETE the object. If you read the definitions, you can easily understand the Bridge Design Pattern.

Real-Time Example of Bridge Design Pattern in C#:

In the Bridge Design Pattern, there are two layers. The first layer is the Abstraction Layer, and the second layer is the Implementation Layer. If we make any changes in the Implementation Layer, it won’t affect the Abstraction Layer. Similarly, if we make any changes in the Abstraction Layer, it won’t affect the Implementation layer.

Please look at the following image to better understand the Bridge Design Pattern. On the left-hand side, you can see the Abstraction. Suppose you want to turn on the TV or turn off the TV, then what you can do here is you can use the Remote Control to turn On/Off the TV. The original TV implementer will do the Implementation. So, in this case, Samsung TV or Sony TV will implement the turn-on or turn-off functionality. So, the abstraction will use one of the implementers to turn on or turn off the TV.

Real-time Example of Bridge Design Pattern in C#

Suppose you want to add a new implementation later, then you can do this in the Implementation Layer. For example, you can add a new TV (Panasonic TV) in the Implementation Layer, which will not affect the Abstraction Layer. You can also add a new Panasonic TV Remote Control in the Abstraction Layer without affecting the Implementation Layer. So, this is one of the best examples of the Bridge Design Pattern.

Note: First, we will implement the Bridge Design Pattern example using C#, and then we will try to understand the Class Diagram or UML Diagram of the Bridge Design Pattern by comparing it with our Example.

Implementation of Bridge Design Pattern using C#:

Let us implement the above example step by step using the Bridge Design Pattern in C#.

Step 1: Creating Abstract LED TV

Create an interface named ILEDTV.cs and copy and paste the following code into it. This interface has three methods (SwitchOff, SwitchOn, and SetChannel). The implementation classes will implement this interface. This Interface acts as a bridge between the abstraction classes and implementor classes. This Interface defines the operations for all implementation classes.

namespace BridgeDesignPattern
{
    // This is going to be an interface that acts as a bridge between the abstraction classes and implementer classes
    // The Implementor Interface defines the operations for all implementation classes.
    // It doesn't have to match the Abstraction's interface. 
    // In fact, the two interfaces can be entirely different. 
    
    public interface ILEDTV
    {
        void SwitchOn();
        void SwitchOff();
        void SetChannel(int channelNumber);
    }
}
Step 2: Creating Concrete LED TV

In our example, we will create two concrete classes, i.e. SamsungLedTv and SonyLedTV. These classes will implement the ILEDTV interface and provide the implementation details for the associated Abstraction class. Each Concrete Implementation corresponds to a specific platform.

SamsungLedTv

Create a class file named SamsungLedTv.cs and copy and paste the following code. This class implements the LEDTV interface and provides implementations for SwitchOn, SwitchOff, and SetChannel methods. Using the SwitchOn method, we can turn On the Samsung TV. Using the SwitchOff method, we can turn Off the Samsung TV, and with the SetChannel method, we can change the channel number of the Samsung TV.

using System;
namespace BridgeDesignPattern
{
    // This is going to be a class which implements the ILEDTV interface and 
    // also provide the implementation details for the associated Abstraction class.
    // Each Concrete Implementation corresponds to a specific platform
    
    public class SamsungLedTv : ILEDTV
    {
        public void SwitchOn()
        {
            Console.WriteLine("Turning ON : Samsung TV");
        }
        public void SwitchOff()
        {
            Console.WriteLine("Turning OFF : Samsung TV");
        }

        public void SetChannel(int channelNumber)
        {
            Console.WriteLine("Setting channel Number " + channelNumber + " on Samsung TV");
        }
    }
}
SonyLedTv:

Create a class file named SonyLedTv.cs and copy and paste the following code. This class also implements the ILEDTV interface and provides implementations for SwitchOn, SwitchOff, and SetChannel methods. Using the SwitchOn method, we can turn On the Sony TV. Using the SwitchOff method, we can turn Off the Sony TV, and using the SetChannel method, we can change the channel number of the Sony TV.

using System;
namespace BridgeDesignPattern
{
    // Each Concrete Implementation corresponds to a specific platform
    // This is going to be a class and should implement the Implementation interface
    public class SonyLedTv : ILEDTV
    {
        public void SwitchOn()
        {
            Console.WriteLine("Turning ON : Sony TV");
        }
        public void SwitchOff()
        {
            Console.WriteLine("Turning OFF : Sony TV");
        }

        public void SetChannel(int channelNumber)
        {
            Console.WriteLine("Setting channel Number " + channelNumber + " on Sony TV");
        }
    }
}
Step 3: Creating Abstract Remote Control

Create a class named AbstractRemoteControl.cs and copy and paste the following code. This class will have three abstract methods (SwitchOn, SwitchOff, and SetChannel). Here, you can give the method names the same as the ILEDTV interface, or you can also give different names. It has one protected variable, ledTv, which will be available to subclasses. 

namespace BridgeDesignPattern
{
    //This is an abstract class that contains members that define an abstract business object and its functionality.
    //It contains a reference to an object of type ILEDTV and delegates all of the real work to this object.
    //It can also act as the base class for other abstractions.
    
    public abstract class AbstractRemoteControl
    {
        protected ILEDTV ledTv;
        public abstract void SwitchOn();
        public abstract void SwitchOff();
        public abstract void SetChannel(int channelNumber);
    }
}
Step 4: Creating Concrete Remote Control

Here, we will create two concrete remote control classes, i.e. SamsungRemoteControl and SonyRemoteControl.

SamsungRemoteControl:

Create a class file named SamsungRemoteControl.cs and copy and paste the following code. This is a concrete class, and it implements the abstract AbstractRemoteControl class and provides the implementation for SwitchOn, SwitchOff, and SetChannel methods. Further, if you notice, the constructor takes one parameter of ILEDTV type, i.e., an instance of one of the child classes of ILEDTV type that we want to access remotely. Further, we are passing that ILEDTV object to the base class constructor.

namespace BridgeDesignPattern
{
    // This is going to be a concrete class which inherits from the Abstraction class i.e. AbstractRemoteControl. 
    // This Redefined Abstraction Class extends the interface defined by AbstractRemoteControl class.
    public class SamsungRemoteControl : AbstractRemoteControl
    {
        public SamsungRemoteControl(ILEDTV ledTv) 
        {
            this.ledTv = ledTv;
        }

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

        public override void SwitchOff()
        {
            ledTv.SwitchOff();
        }

        public override void SetChannel(int channelNumber)
        {
            ledTv.SetChannel(channelNumber);
        }
    }
}
SonyRemoteControl:

Create a class file named SonyRemoteControl.cs and copy and paste the following code. This will also be a concrete class that implements the AbstractRemoteControl class and provides the implementation for SwitchOn, SwitchOff, and SetChannel methods. Further, if you notice, the constructor takes one parameter of ILEDTV type, i.e., an instance of one of the child classes of ILEDTV type, which we want to access remotely. Further, we are passing that ILEDTV object to the base class constructor.

namespace BridgeDesignPattern
{
    // This is going to be a concrete class which inherits from the Abstraction class i.e. AbstractRemoteControl. 
    // This Redefined Abstraction Class extends the interface defined by AbstractRemoteControl class.
    public class SonyRemoteControl : AbstractRemoteControl
    {
        public SonyRemoteControl(ILEDTV ledTv)
        {
            this.ledTv = ledTv;
        }

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

        public override void SwitchOff()
        {
            ledTv.SwitchOff();
        }

        public override void SetChannel(int channelNumber)
        {
            ledTv.SetChannel(channelNumber);
        }
    }
}
Step5: Client Class

In our example, the Program class will be the Client Code. So, please modify the Main method of the Program class as shown below. First, we are using the SonyRemoteControl and attaching the SonyLedTv to access the Sony LED TV remote (i.e., Turn On, Change Channel, and Turn Off). We can also do the same thing using SamsungRemoteControl to access the SansungLedTv remote.

using System;
namespace BridgeDesignPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            // Except for the initialization phase, where an Abstraction object gets i.e. SonyRemoteControl or SamsungRemoteControl
            // linked with a specific Implementation object i.e. new SonyLedTv() or new SamsungLedTv(), 
            // the client code should only depend on the Abstraction class i.e. SonyRemoteControl or SamsungRemoteControl. 
            AbstractRemoteControl sonyRemoteControl = new SonyRemoteControl(new SonyLedTv());
            sonyRemoteControl.SwitchOn();
            sonyRemoteControl.SetChannel(101);
            sonyRemoteControl.SwitchOff();

            Console.WriteLine();

            AbstractRemoteControl samsungRemoteControl = new SamsungRemoteControl(new SamsungLedTv());
            samsungRemoteControl.SwitchOn();
            samsungRemoteControl.SetChannel(202);
            samsungRemoteControl.SwitchOff();

            Console.ReadKey();
        }
    }
}
Output:

Implementation of Bridge Design Pattern in C#

I hope you understand How to Implement the Bridge Design Pattern in C#. So, let us try to understand the Class Diagram or UML Diagram and understand the different components or participants involved in the Bridge Design Pattern.

Understanding the Class Diagram or UML Diagram of Bridge Design Pattern:

Please look at the following image to understand the class diagram or UML Diagram of the Bridge Design Pattern in C#.

Class Diagram or UML Diagram of Bridge Design Pattern

The above image shows that the bridge design pattern consists of four participants. They are as follows.

  1. Implementer: This is an interface, and all the implementation classes must implement this interface. This interface is going to act as a bridge between the abstraction classes and the implementer classes. In our example, it is the ILEDTV interface.
  2. ConcreteImplementationA / ConcreteImplementaionB: These will be classes and implement the Implementor (ILEDTV) interface. In our example, it is our SamsungLedTv and SonyLedTv classes. These classes contain the concrete implementation of all the operations (in our example implementation for SwitchOn, SwitchOff, and SetChannel methods).
  3. Abstraction: This is going to be an abstract class. In our example, it is the AbstractRemoteControl class. It defines the methods (in our example, SwitchOn, SwitchOff, and SetChannel) for the Client Code to call. The protected implementer variable (in our example, the ledTv variable) refers to the object performing the implementation.
  4. ConcreteAbstraction / RefinedAbstraction: ConcreteAbstractions are the concrete classes inherited from the Abstraction abstract class. In our example, it is the SamsungRemoteControl and SonyRemoteControl.
Advantages of Bridge Design Pattern:
  • Decoupling: It decouples an abstraction from its implementation, allowing them to vary independently.
  • Single Responsibility Principle: It promotes the principle by separating an abstraction from its implementation.
  • Flexibility: Increases the flexibility in terms of the framework and its implementation.
  • Extensibility: Both the abstractions and implementations can be extended independently.
  • Prevents Cartelization: Avoids the ‘cartesian product’ complexity explosion. For example, if you have N abstractions and M implementations, you don’t need N*M classes.
When to Use Bridge Design Pattern in C# Real-Time Applications?

The Bridge Design Pattern in C# is particularly useful in scenarios where:

  • Abstraction and Implementation Can Vary Independently: When you want to decouple an abstraction from its implementation so that the two can vary independently. This is useful in cases where, for instance, the core functionality and the platform-specific details need to be developed and extended separately.
  • Changing Implementation at Runtime: If your application needs to switch between different implementations at runtime. The Bridge pattern allows you to change the implementation dynamically without altering the abstraction.
  • Extending Classes in Separate Dimensions: When you have multiple dimensions in your class hierarchy that need to be extended independently. For example, if you have a UI framework, you might want to extend UI controls independently from operating system-specific behaviors.
  • Avoiding a Permanent Binding to Implementation: In scenarios where a permanent binding between the abstraction and its implementation might limit the flexibility and future scalability of the code.
  • Sharing an Implementation Among Multiple Objects: When you need to share an implementation among multiple objects. The Bridge pattern allows multiple abstractions to use the same implementation, which can be more efficient.
  • Platform Independence: It’s particularly useful in cross-platform applications where you want to hide the platform-specific code from the high-level logic.
  • Preventing Exponential Class Explosion: In cases where a class hierarchy would result in an exponential number of combinations due to the various dimensions that can be extended. The Bridge pattern prevents this by separating the hierarchies.
  • Long-term Stability of Abstraction and Implementation: When the parts of a system that represent high-level logic (abstraction) and low-level platform details or back-end logic (implementation) are subject to different rates of change or different types of change.

In the next article, I will discuss Real-Time Examples of Bridge Design Patterns using C#- Send Messages. In this article, I try to explain the Bridge Design Pattern in C# with Examples. I hope you understand the need and use of the Bridge Design Pattern in C# with Examples.

3 thoughts on “Bridge Design Pattern in C#”

  1. why didn’t you use a single class with name “RemoteControl” instead of “SamsungRemoteControl” and “SonyRemoteControl”?
    They work same as each other and in constructor you are sending “LEDTV” to the class. You can use it for calling related methods.

    1. The names of them are the same, but they are different.
      In other words, there are two different methods with different full names. See below:

      samsungRemoteControl.SwitchOn() => “Turning ON : Samsung TV”
      sonyRemoteControl.SwitchOn() => “Turning ON : Sony TV”

      If you want to implement them with one class “LEDTV”, you need to change the code of it, whenever you add a new brand. Why is it bad? Go below :
      https://dotnettutorials.net/lesson/open-closed-principle/

  2. Can you tell me what is wrong with this option?

    void Main()
    {
    AbstractPersistanceData _nfs_data = new RemoteControlData(new NFSPersistanceData());
    _nfs_data.Delete();
    _nfs_data.Save();

    AbstractPersistanceData _db_data = new RemoteControlData(new DBPersistanceData());
    _db_data.Delete();
    _db_data.Save();
    }

    public interface IPersistanceData
    {
    void Save();
    void Delete();
    }

    public class NFSPersistanceData : IPersistanceData
    {
    public void Delete()
    {
    Console.WriteLine(“NFS Deleted data”);
    }

    public void Save()
    {
    Console.WriteLine(“NFS Save data”);
    }
    }

    public class DBPersistanceData : IPersistanceData
    {
    public void Delete()
    {
    Console.WriteLine(“DB Deleted data”);
    }

    public void Save()
    {
    Console.WriteLine(“DB Save data”);
    }
    }

    public abstract class AbstractPersistanceData
    {
    protected IPersistanceData data;
    public abstract void Save();
    public abstract void Delete();
    }

    public class RemoteControlData : AbstractPersistanceData
    {
    public RemoteControlData(IPersistanceData data) => base.data = data;

    public override void Delete()
    {
    data.Delete();
    }
    public override void Save()
    {
    data.Save();
    }
    }

Leave a Reply

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