Bridge Design Pattern Using C#

The Bridge Pattern is part of the Structural Design patterns. As the name may suggest, it acts as an intermediary between two components. But if we talk about the Adapter Pattern then both patterns have the same logical definition. To say that it acts as an intermediary is partially correct. Both are a type of intermediary between two systems, but the actual difference is the goal that these patterns achieve. The Adapter Pattern is designed to act as an intermediary when the two components are not compatible to work with each other. Also the Adapter Pattern can add more functionality to a source component request, before passing it on to the target component, with which the source component is to interact. On the other hand, the Bridge Pattern's purpose is to provide multiple pathways between two components that are nothing but achieving many to many communication between multiple implementations of the source and the target components. So technically, it receives a request from one of the many implementations of the source component, and based on the client requirements, sends it to one of the many implementations of the target component.

What a Bridge Pattern is

According to the GoF's definition, this pattern "separates an object's interface from its implementation".

Separates an object's interface from its implementation

As per the preceding definition, it helps us to create a structure, where even the interface is separated from the implementation using a bridge. Technically this results in a system where a function can have multiple implementations and each implementation of that function can be used in multiple ways. So this results in a system with many-to-many mappings. Let's explain this using a real-world example.

A real-world example

Before we start with the example, let's explain the basic requirements we have. We have an abstract business logic to perform some operation (like saving to a database or sending notifications to the user) and this operation can be done in multiple ways.

Let's use an example of a system that is capable of sending Emails and SMSs using a web-service created by you. So what will we have in the code? An interface that will have a method declaration, to send the email and SMS, implemented by the Email and the SMS concrete classes.

So in our example, sending the SMS and Email notifications to users is our business logic and sending it through a Web service, is one of the many different ways to do it.

So our code will become something like the following:

Design-Pattern1

Fine until now. Consider a case where you are required to add the ability to send the Emails and SMS's using a third-party API. What to do now is to introduce another interface, with 2 more implementation classes. So the number of classes increases. Another way to send SMS and Email using a WCF service is relvant and you again add one more type of interface and its 2 different implementations . So you can see here:

  • Started with 1 interface and its 2 implementations - 3 Classes(including the interface to be precise). Then
  • Added one more interface for third-party API and its 2 implementations - 3 + 3 (from step 1) = 6 classes (including 2 interfaces). Then
  • Added the third interface for sending using WCF service its 2 implementations - 3 +6 (step 1 and 2 classes).

The total reaches 9 and this will keep on increasing, as the number of methods to implement the logical code continues increasing. Further if more ways to send user notifications are added then they will add more classes to the system and it will become difficult to maintain the code and make changes in the future.

This is where the Bridge Pattern is useful and provides multiple ways to complete a task that has multiple implementations. This is what we meant by many-to-many mappings at the start of the explanation. So let's explain how to avoid this situation using this pattern.

To solve this problem, we introduce another level of abstraction. This time, it's not for the logic implementation, but for the various ways in which we can call this logic. So we create an abstract bridge that will be used to link the two systems, in other words various ways to send various types of user notifications.

Our system code will be divided into the following 4 components:

  1. Abstraction

    This will be the abstract class that will have the abstract logic to be implemented. Most importantly, this will hold a reference to the bridge (that will internally have a reference to the system through which notification is to be sent). The rest is just like any other interface-based definition of the functions to be implemented.
     
  2. Abstraction Details

    These will be concrete implementations of the abstraction of Step 1.
     
  3. Bridge Abstraction

    This is the abstract component that will act as a bridge between the two components.
     
  4. Bridge Implementations

    These are the implementations for the bridge and will provide various ways in which we can call the required logic implementations of Step 2.


So our first step will be to define the the bridge abstraction and the logic abstraction classes. Our logic abstraction class will have a reference to the abstract bridge. Next, we will implement the abstract logic and call the bridge method, using its reference, that these concrete logic based classes hold withing them. See the code below:

Design-Pattern2

Next we will be have multiple implementations of the bridge, that is nothing but different ways in which we can send these user notifications. So this is nothing but another implementation of the interface, but this time its the Bridge interface.

Design-Pattern3

Finally we will write the client code.

Design-Pattern4

So run the code and see the results.

How it works

Now let's see what actually is happening. We are setting the basic properties of our class SendData, like we would do for any other class. One of these properties is the abstract bridge reference in the form of a property named _iBridgeComponents. So this property is basically assigned the actual or the concrete bridge that we want to use for the logic processing. In first case, its the web service and in the second case its the third-party API. So when we invoke the Send() function using this property, it is actually invoking the Send() function of the class for which it holds the reference. In the first case this happens to be the web service and in the second case, its the third-party API.
At the beginning of this article, we explained how the number of classes can increase without this pattern. Here we have 6 classes (including the interface). Even if we add another method using a WCFService, we will have only one more class added to the system, that makes it 7. So in the future, even if we keep adding the logic implementations and the methods to do it, the number of classes does not increases, as it would have without this pattern. Hope this makes it clear.

So this was all about the Bridge Design Pattern.


Similar Articles