Bridge Design Pattern in C#: Everything You Need to Know


Suppose you are working on a project that involves communication or interaction between different cross-platform applications. They are working with numerous API providers of a specific type and support multiple types of database servers. In that case, you can surely use the Bridge design pattern to make things easy for yourself.

The Bridge design pattern is used to decouple an abstraction from its implementation. As a result, the abstraction and implementation can be developed and changed independently. This pattern is often used to enable the separation of concerns.

In this article, you will learn about the Bridge design pattern with examples and its practical implementation to solve real problems in software design.

What is the Bridge design pattern?

bridge design pattern in a nutshell

The Bridge design pattern is a software design pattern that is used to decouple an abstraction from its implementation. It can separate the responsibility of an object into two separate parts – the abstraction and the implementation. The two parts can be worked on independently of each other.

You can use the Bridge design pattern to create a layer of abstraction between the user interface and the business logic. The Bridge can be helpful in cases where the user interface needs to be changed without affecting the business logic.

The Bridge pattern is one of the 7 structural design patterns.

Consider that there is a new version of the .NET framework that you use to compile and run the C# code, and you have numerous versions of the Framework loaded on your computer that you can choose. You can set up the path externally in your Operating System to select a specific version. That selection would be considered as the “Bridge” between what applications want and what the actual versions they would get, so it is many-to-many mapping for you.

The Bridge pattern is helpful whenever a new software version is introduced to take over the older version, but the older version still needs to work for the current client base. The significant thing is that the code for the clients’ side doesn’t need any changes since it is written according to the abstraction, except for a single thing that client would provide info about the version it wants to use.

When to use the Bridge Design Pattern?

After understanding the Bridge design pattern, there might be confusion about when to use it.

It would be quite helpful to use the Bridge design pattern in the following scenarios:

  • If you want to avoid coupling between abstraction and implementation.
  • When abstraction and their implementation both are extensible.
  • If amendments in implementation can affect the client.
  • When you want to share the implementation among various clients.
  • When you want to organize and split a monolithic class with numerous functionality alternatives.

Let’s dive into the details of all these scenarios!

Coupling avoidance between abstraction and implementation

There would be a time when you want to avoid coupling between the abstraction and its implementation in your software development journey. Such cases appear under certain conditions, such as when there is a chance that implementation would be switched or selected at the run time, as discussed earlier.

Both abstraction and implementation are extensible

Suppose you are working on a project where both abstraction and implementation are open to extension. In that case, the Bridge pattern allows you to collab different abstractions and implementations that you can extend independently of each other.

Changes to implementation can affect the client

If you do not prefer that any changes done in the implementation would affect your client, or there wouldn’t be a need to recompile the code at the client’s side after some changes in implementation, then you should surely use the Bridge pattern. The Bridge pattern provides the perfect solution to avoid such cases.

Share the implementation among multiple clients

If there is a need to share an object among multiple other objects, then the Bridge pattern is a perfect fit for this job. For instance, a case where an employee class object is shared among various other classes of an employee management system to implement the complete business logic of the software.

To organize and split a monolithic class

A monolithic class having numerous adaptations or alternatives of some functionality often needs to be divided and organized to make the design easy and avoid complications. However, there can be a lot of complications with such classes since it is not easy to make changes as it will take more time and effort to make any amendments. Also, it would be more difficult to comprehend it, and minor changes will lead to complex and massive errors. So Bridge pattern will provide the perfect solution for this by dividing the monolithic class into various classes such that each hierarchy is entirely independent of the other. Hence it will make the code easy to understand and make changes.

Who are the participants of the Bridge pattern? – Structural code in C#

The UML diagram below shows the sample implementation of the Bridge design pattern in C#.

bridge pattern UML diagram

The participants in this pattern are:

  • Abstraction – The interface shown to the client through which the client interacts.
  • RefinedAbstraction – Extends the Abstraction interface.
  • Implementor – It defines the interface for implementation classes. It may differ from the Abstraction interface. This is the bridge class.
  • ConcreteImplementor – The actual implementations of the Implementor interface.

Let’s look at the sample code of the Bridge Design Pattern in C#.

class Abstraction
{
    Bridge bridge;
    public Abstraction(Bridge implementation)
    {
        bridge = implementation;
    }
    public String Operation()
    {
        return "Abstraction << BRIDGE >> " + bridge.OperationImp();
    }
}
interface Bridge
{
    string OperationImp();
}

class ImplementationA : Bridge
{
    public string OperationImp()
    {
        return "ImplementationA";
    }
}

class ImplementationB : Bridge
{
    public string OperationImp()
    {
        return "ImplementationB";
    }
}

Usage:

Console.WriteLine("Bridge Pattern\n");
Console.WriteLine(new Abstraction(new ImplementationA()).Operation());
Console.WriteLine(new Abstraction(new ImplementationB()).Operation());

Output:

Bridge Pattern

Abstraction << BRIDGE >> ImplementationA
Abstraction << BRIDGE >> ImplementationB

Bridge design pattern – Real-world example in C#

Consider software with a new version named “ShareJornal“. It allows users to register and write a few things about their daily routine and interactions with their close friends, just like a daily journal. This software will require no user authentication or instantiation, and you would consider that the user will be entered immediately into the system for simplicity.

The previous version of the software called “DailyJournal” is not discarded yet, and the users can also access the pages on the previous version. So try to think about it a little bit how you can fulfill this requirement with the help of the Bridge design pattern.

Now let’s take a look at the solution.

//This is the Abstraction
class JournalAbstraction
{
    JournalImplementation _implementation;
    public JournalAbstraction(JournalImplementation implementation)
    {
        _implementation = implementation;
    }

    public void Add(string message)
    {
        _implementation.Add(message);
    }
    public void Add(string friend, string message)
    {
        _implementation.Add(friend, message);
    }
    public void Poke(string who)
    {
        _implementation.Poke(who);
    }
}

//This is the Implementor interface
interface JournalImplementation
{
    void Add(string message);
    void Add(string friend, string message);
    void Poke(string who);
}

//This is the ConcreteImplementor1
public class ShareJournal : JournalImplementation
{
    DailyJournal _myDailyJournal;
    string _name;
    static int _users;

    public ShareJournal(string name)
    {
        _name = name;
        _users++;
        _myDailyJournal = new DailyJournal(_name + " daily journal");
    }

    public void Add(string message)
    {
        _myDailyJournal.Add(message);
    }

    public void Add(string friend, string message)
    {
        _myDailyJournal.Add(friend, _name + " said:" + message);
    }

    public void Poke(string who)
    {
        _myDailyJournal.Poke(who);
    }
}

//This is the ConcreteImplementor2
public class DailyJournal : JournalImplementation
{
    static SortedList<string, DailyJournal> community 
        = new SortedList<string, DailyJournal>(100);
    string pages;
    string name;
    string gap = "\n\t\t\t\t";

    public DailyJournal(string n)
    {
        name = n;
        community[n] = this;
    }

    public void Add(string s)
    {
        pages += gap + s;
        Console.Write(gap + " ======== " + name + "'s " +
            "Spacebook ========");
        Console.Write(pages);
        Console.WriteLine(gap + "=================================");
    }

    public void Add(string friend, string message)
    {
        community[friend].Add(message);
    }

    public void Poke(string who)
    {
        community[who].pages += gap + "You have been poked";
    }
}

Usage:

DailyJournal me = new DailyJournal("Lance");
me.Add(" Hello world ");
me.Add(" Today, I have spent a really busy day ");

var clara = new JournalAbstraction(new ShareJournal("Clara"));
clara.Poke("Lance");
clara.Add("Lance", "How is everything going?");
clara.Add("Hello there! I have started to write on ShareJournal");

Output:

 ======== Lance's Spacebook ========
 Hello world
=================================

 ======== Lance's Spacebook ========
 Hello world
 Today, I have spent a really busy day
=================================

 ======== Lance's Spacebook ========
 Hello world
 Today, I have spent a really busy day
You have been poked
Clara said:How is everything going?
=================================

 ======== Clara daily journal's Spacebook ========
Hello there! I have started to write on ShareJournal

What are the advantages of using the Bridge pattern?

There are various perks of using a Bridge design pattern. Some of them are listed here:

  • You can develop the classes and applications without caring about the platform.
  • The client interacts with a high level of abstraction without knowing about the implementation details.
  • The Bridge pattern will allow you to extend the implementation and Bridge classes independently.
  • Bridge pattern makes the maintenance of the code much easier.
  • With the help of the Bridge design pattern, code duplication is discouraged, and code reusability is fostered.

What are the disadvantages of using the Bridge design pattern?

With a lot of ease, there are some cons to the Bridge design pattern.

  • With the usage of the Bridge design pattern, the restriction of independent functionality needs to be fulfilled.
  • With the Bridge design pattern, the functionality calling request is forwarded to the implementer from the abstraction, so a type of indirection is introduced in the system.

What are related patterns to the Bridge Design Pattern?

The patterns that are related to the Bridge design pattern are the Adapter pattern and the Abstract Factory pattern. You can use the Abstract Factory pattern with the Bridge design pattern as the Abstract Factory pattern can create and configure the Bridge.

The Adapter pattern is similar to the Bridge pattern since both act as intermediaries between the two system’s components. But, they differ in purpose. The Adapter pattern makes the two non-compatible components work together. On the other hand, the Bridge pattern allows the abstraction and implementation to vary independently.

Conclusion

This article taught you about the Bridge design pattern, real-world examples, and its practical implementation.

The Bridge design pattern is a powerful tool to help you write more flexible and extensible code. It is easy to learn and use and can make a big difference in the quality of your code. So, the next time you need to decouple abstraction and implementation, use the Bridge pattern to achieve the desired goal.

If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.

Recent Posts