Bridge Pattern

Hello everyone, and welcome to .NET Core Central. In this blog post, I am going to walk through the Bridge Pattern.

The bridge design pattern is one of the patterns from the gang of four design patterns. The bridge pattern is a structural design pattern.

The main intent of the bridge design pattern is to decouple an abstraction from its implementation so that they both can vary independently.

Bridge Pattern Usecase

From the intent of the pattern, it is a little bit hard to understand exactly what this means. So I am going to first start with an example that does not implement the bridge pattern. And then I will go ahead and update the solution using the bridge pattern. That will make things clear.

For the purpose of demonstrating the bridge pattern, let us build a notification system. For the demonstration, the notification system will just print responses into the consol, instead of sending them externally using email or text.

NotificationProcessor

Let us consider we have an abstract class called NotificationProcessor. The NotificationProcessor class has a single abstract method ProcessNotification. The ProcessNotification method will have a single input parameter message of type string.

The ProcessNotification class is the abstraction in the context of the bridge design pattern.

public abstract class NotificationProcessor
{
    public abstract void ProcessNotification(string message);
}

TextNotificationProcessor

After defining the abstract class NotificationProcessor, we will define another class called TextNotificationProcessor. The TextNotificationProcessor class will derive from the NotificationProcessor abstract class.

Since the TextNotificationProcessor class is derived from the abstract class TextNotificationProcessor, it will implement the abstract method ProcessNotification. And inside the ProcessNotification method, it will set the protected variable notificationMessage with the incoming message parameter.

public class TextNotificationProcessor : NotificationProcessor
{
    protected string notificationMessage;
    public override void ProcessNotification(string message)
    {
        notificationMessage = message;
    }
}

TextNotificationSender

And then finally we have another class called TextNotificationSender. The TextNotificationSender class is responsible for sending text notifications.

The TextNotificationSender class derives from the TextNotificationProcessor, and then it overrides the ProcessNotification method.

Inside the ProcessNotification method, we will first we will call the ProcessNotification of the base class. And then finally we will send the notification, but for this example, we will just write the notification message in the console output.

public class TextNotificationSender : TextNotificationProcessor 
{
    public override void ProcessNotification(string message)
    {
        base.ProcessNotification(message);
        // Send message here
        Console.WriteLine($"Text: {notificationMessage}");
    }
}

EmailNotificationProcessor

Similar to the TextNotificationProcessor, we have another class EmailNotificationProcessor for sending emails, which behaves similar to the TextNotificationProcessor.

The EmailNotificationProcessor class will also derive from the NotificationProcessor abstract class. And it will implement the abstract method ProcessNotification.

Since it is email notification, it uses HTML content along with the input parameter message, inside the ProcessNotification method. And it will set the protected variable notificationMessage with the HTML content.

public class EmailNotificationProcessor : NotificationProcessor
{
    protected string notificationMessage;
    public override void ProcessNotification(string message)
    {
        notificationMessage = $"<html>{message}</html>";
    }
}

EmailNotificationSender

Finally, we will have a class for sending the email notification. The EmailNotificationSender class will have a similar implementation as the TextNotificationSender class.

The EmailNotificationSender class will derive from the EmailNotificationProcessor, and then it overrides the ProcessNotification method.

Inside the ProcessNotification method, similar to the TextNotificationSender, we will first we will call the ProcessNotification method of the base class. And then finally we will write the notification message in the console output.

public class EmailNotificationSender : EmailNotificationProcessor
{
    public override void ProcessNotification(string message)
    {
        base.ProcessNotification(message);
        // Send message here
        Console.WriteLine($"Email: {notificationMessage}");
    }
}

Running the application

This is a normal hierarchical implementation for a notification system. Now, if we want to send a text notification, we can still use NotificationProcessor as the contract, and then we can pass in the instance of TextNotificationSender to send the notification.

To show it working, I am going to use the NotificationProcessor as a dependency to a new minimal API with route /notification.

using BridgePattern.Demo;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSingleton<NotificationProcessor, TextNotificationSender>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapPost("/notification", (NotificationProcessor notificationProcessor, [FromBody] string message) =>
{
    notificationProcessor.ProcessNotification(message);
    return Results.Ok();
})
.WithName("Notification");

app.Run();

In the above implementation, I am calling the ProcessNotification method of the NotificationProcessor abstract base class. And I am passing the message coming as a part of the POST request body.

And during the dependency injection registration, I am registering the TextNotificationSender as the implementation type for the contract NotificationProcessor. Hence inside the minimal API when we call the ProcessMessage, it will call the implementation from the TextNotificationSender class.

Now if we run the application and execute the API from the Swagger UI, we will see the message set in the POST request body will be printed in the console output.

bridge pattern

Issues with this implementation

So now let us discuss what is the problem with this implementation, and the reason why do we need to change this implementation. The main problem with this implementation is this inheritance hierarchy.

What is the problem with inheritance hierarchy? Inheritance hierarchy is not problematic if it is done correctly. But for this implementation, the inheritance hierarchy creates a problem.

For example, if we now make changes to the contract, which is the NotificationProcessor abstract class in our case. All the implementation of the TextNotificationSender or the EmailNotificationSender will also be impacted apart from the TextNotificationProcessor and EmailNotificationProcessor. Because they are tightly coupled, any contract change to the NotificationProcessor abstract class will change the implementation of the derived classes also.

The solution with Bridge Pattern

The question is how do we decouple this tight coupling between abstraction and its implementation. That is where the bridge pattern comes into play.

As I mentioned earlier in this blog post, the main intent of the bridge pattern is to decouple the implementation from the abstraction.

So how can we do that for this example?

Create the INotificationProcessor interface

First of all, we will change the implementation of the NotificationProcesser from a class to an interface with the name INotificationProcessor.

public interface INotificationProcessor
{
    void ProcessNotification(string message);
}

Next, we will update the implementation of the TextNotificationProcessor to implement the interface INotificationProcessor. And the ProcessNotification method will not be abstract anymore. Also, let us make the notificationMessage variable private.

public class TextNotificationProcessor : INotificationProcessor
{
    private string notificationMessage;
    public void ProcessNotification(string message)
    {
        notificationMessage = message;
    }
}

Create INotificationSender interface

We will create a new interface INotificationSender. This interface will have the contract or sending notification. It will define a single method SendNotification, which will have a parameter message of type string.

public interface INotificationSender
{
    void SendNotification(string message);
}

Update TextNotificationProcessor

After we define the INotificationSender interface, we will update the implementation of the TextNotificationProcessor to use this new interface.

public class TextNotificationProcessor : INotificationProcessor
{
    private readonly INotificationSender notificationSender;

    public TextNotificationProcessor(INotificationSender notificationSender)
    {
        this.notificationSender = notificationSender;
    }
    public void ProcessNotification(string message)
    {
        // Do processing of data here and send notification
        notificationSender.SendNotification(message);
    }
}

So here for the TextNotificationProcessor, we will introduce a constructor, where we will inject the INotificationSender interface. And in ProcessNotification, instead of setting a local variable, we will do the necessary processing. And after that, we will call the SendNotification on the INotificationSender instance.

Update TextNotificationSender

Next, we will update the implementation of the TextNotificationSender class. And instead of deriving from the TextNotificationProcessor, this class will now implement the interface INotificationSender.

public class TextNotificationSender : INotificationSender 
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Text: {message}");
    }
}

Update Email related classes

Similarly, we will update the EmailNotificationProcessor will be implementing the INotificationProcessor interface.

public class EmailNotificationProcessor : INotificationProcessor
{
    private readonly INotificationSender notificationSender;

    public EmailNotificationProcessor(INotificationSender notificationSender)
    {
        this.notificationSender = notificationSender;
    }
    public void ProcessNotification(string message)
    {
        notificationSender.SendNotification($"<html>{message}</html>");
    }
}

Finally, we will change the implementation of the EmailNotificationSender to implement the interface INotificationSender.

public class EmailNotificationSender : INotificationSender
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Email: {message}");
    }
}

Here we created a bridge between the Notification Processor and the Notification Sender. Because the notification processor’s implementation is using the notification sender to send the notification.

By implementing this pattern, what we have done is, if anything changes is a part of INotificationProcessor will not impact the implementation of INotificationSender. And this is how we decoupled the abstraction from its implementation.

One important point to note here is that if we implement the SOLID design principles properly, this will be the default implementation. If we are dealing with a notification message and we want to process the notification as well as send the notification, we will never create a class hierarchy.

And we will always end up creating a notification sender which is a single responsibility class responsible for notification sending. And notification processor is like a controller which processes the notification and uses the notification sender to send the notification. so as you can see this implementation or the bridge pattern will happen out of the box if we are just using SOLID design principles.

Run the bridge pattern implementation

Now to run the application, first, we will change the dependency injection registration and add the new class/interface created for the Text notification.

And we will also update the dependency of the API to use the INotificationProcessor interface instead of the earlier NotificationProcessor abstract class.

using BridgePattern.Demo;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddSingleton<INotificationProcessor, TextNotificationProcessor>();
builder.Services.AddSingleton<INotificationSender, TextNotificationSender>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapPost("/notification", (INotificationProcessor notificationProcessor, [FromBody] string message) =>
{
    notificationProcessor.ProcessNotification(message);
    return Results.Ok();
})
.WithName("Notification");

app.Run();

Now if we run the application, we will see the exact same response as before.

Conclusion

This is a very simple implementation of the bridge pattern. And as I mentioned initially, back in the days before the SOLID design principle, it really made sense of using the bridge pattern in certain scenarios.

But after I started using the SOLID design principles, I personally feel that the bridge pattern kind of comes in for free with the SOLID design principles. If we use the SOLID principles properly, we will end up getting into the bridge pattern out of the box.

Because with SOLID, we will never have one hierarchical giant implementation for anything. We will always break things down into individual single responsibility classes and interfaces.

A Youtube video for this implementation is here.