DEV Community

Cover image for The Bridge Pattern: Simplifying Code Structure with Flexibility
Daniel Azevedo
Daniel Azevedo

Posted on

The Bridge Pattern: Simplifying Code Structure with Flexibility

Hi devs :)

If you’ve been diving deep into design patterns, you might’ve come across the Bridge Pattern. It’s one of those patterns that seems tricky at first but can be a lifesaver when you need to decouple abstraction from implementation. Let’s break it down in a simple way, using a real-world scenario.

What is the Bridge Pattern?

The Bridge Pattern is a structural design pattern that helps to separate an abstraction from its implementation, allowing both to evolve independently. This is particularly useful when dealing with multiple layers of abstraction or platforms. Essentially, it’s about creating a bridge between two different components, so changes in one don’t affect the other.

In simpler terms, imagine you have different types of devices (like a phone or a tablet) and different operating systems (iOS, Android, etc.). You want to extend both the devices and the OS without directly coupling them together. This is where the Bridge Pattern shines!

The Problem

Let’s take an example from a more relatable industry like HR systems, specifically around salary processing.

In a typical HR system, you might have different ways of calculating salaries, for example:

  • Hourly employees: Paid based on hours worked.
  • Salaried employees: Paid a fixed monthly amount.
  • Contractors: Paid based on project deliverables.

At the same time, there could be different payment methods, like bank transfer, cheque, or digital wallets. Managing all these combinations without creating a web of spaghetti code can be challenging. Without a flexible approach, you would end up writing repetitive code for each combination of employee type and payment method. This would make your system hard to maintain and scale.

How the Bridge Pattern Helps

The Bridge Pattern allows you to separate the logic for employee types and payment methods. Here’s how you can implement this in C#:

Step 1: Create the Abstraction

We start by creating an abstraction for the employee types.

public abstract class Employee
{
    protected IPaymentProcessor paymentProcessor;

    protected Employee(IPaymentProcessor paymentProcessor)
    {
        this.paymentProcessor = paymentProcessor;
    }

    public abstract void ProcessSalary();
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Implementations

Next, we create specific employee types by extending the base class.

public class SalariedEmployee : Employee
{
    public SalariedEmployee(IPaymentProcessor paymentProcessor) : base(paymentProcessor)
    {
    }

    public override void ProcessSalary()
    {
        Console.WriteLine("Processing salary for salaried employee.");
        paymentProcessor.ProcessPayment();
    }
}

public class HourlyEmployee : Employee
{
    public HourlyEmployee(IPaymentProcessor paymentProcessor) : base(paymentProcessor)
    {
    }

    public override void ProcessSalary()
    {
        Console.WriteLine("Processing salary for hourly employee.");
        paymentProcessor.ProcessPayment();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Define the Payment Processor Interface

Here’s where the bridge comes in. The payment methods are now decoupled from employee types. We create a separate interface for payment processing.

public interface IPaymentProcessor
{
    void ProcessPayment();
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Implement the Payment Methods

Now we can create concrete implementations for the different payment methods.

public class BankTransferProcessor : IPaymentProcessor
{
    public void ProcessPayment()
    {
        Console.WriteLine("Processing payment via bank transfer.");
    }
}

public class ChequeProcessor : IPaymentProcessor
{
    public void ProcessPayment()
    {
        Console.WriteLine("Processing payment via cheque.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Putting it All Together

With this structure, we can now combine any type of employee with any payment method without having to modify existing code.

class Program
{
    static void Main(string[] args)
    {
        // Salaried employee with bank transfer payment
        Employee salariedEmployee = new SalariedEmployee(new BankTransferProcessor());
        salariedEmployee.ProcessSalary();

        // Hourly employee with cheque payment
        Employee hourlyEmployee = new HourlyEmployee(new ChequeProcessor());
        hourlyEmployee.ProcessSalary();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Processing salary for salaried employee.
Processing payment via bank transfer.
Processing salary for hourly employee.
Processing payment via cheque.
Enter fullscreen mode Exit fullscreen mode

Why is this helpful?

Using the Bridge Pattern makes the system scalable and easy to maintain. You can add new employee types or payment methods without changing the existing codebase. If tomorrow you decide to implement digital wallet payments or add another employee type like freelancers, you can do that without touching the core logic.

When to Use the Bridge Pattern

  • When you need to decouple abstraction from its implementation.
  • When both abstraction and implementation need to extend independently.
  • When changes to one class should not require changes in others.

Final Thoughts

The Bridge Pattern is a powerful way to manage complexity and keep your codebase flexible. By decoupling abstraction from implementation, you can avoid creating tightly coupled, hard-to-maintain systems. In scenarios like salary processing, this pattern can be a game changer!

Keep coding

Top comments (0)