Easy Patterns in Python: The Bridge

Introduction

The Bridge pattern is a design pattern that is meant to “decouple an abstraction from its implementation so the two can vary independently”. To that end this pattern can use encapsulation, aggregation and also inheritance in order to separate responsibilities.

Since this all sounds rather cryptic, why not look at the UML diagram?

As you can see, there are two hierarchies at work here:

  1. The Abstraction hierarchy
  2. The Implementor/Implementation hierarchy.

This separation makes the two independent of each other or in other words, separates them. As you can see the operation() method is delegated to a class implementing Implementor interface, so these classes can be changed easily.

So, what problems does this pattern solve?

  1. When you need to indepently define or extend either the implementation or the abstraction
  2. If you need to avoid a compile-time binding between implementation and abstraction, but instead want to establish this at run-time

Implementation in Python

In this example we will deal with two webshops, webshopA and webshopB, and two payment methods: cash and creditcard.

We will start by defining the PaymentMethod base class:

class PaymentMethod:
    def pay(self):
        pass

In our simplified example, all a Payment method needs to do is pay the bill.

Next we define the CreditCard payment method:

class CreditCard(PaymentMethod):
    def pay(self):
        print("Paying with credit card")

Paying in this context means nothing more than just issuing a message.

The Cash payment method is similar:

class Cash(PaymentMethod):
    def pay(self):
        print("Paying with cash")

Now we need to define the Webshop interface:

class Webshop:
    def check_out(self):
        pass

A short explanation:

  1. The Checkout() method is where the Pay() method to the PaymentMethod class gets called
  2. The payment method can be changed, we do that using the @property syntax.

Now we define our first shop:

class WebshopA(Webshop):
    _method: PaymentMethod = None

    def __init__(self, new_method: PaymentMethod):
        self._method = new_method

    def check_out(self):
        print("Starting checkout for webshop A")
        self._method.pay()

    @property
    def method(self) -> PaymentMethod:
        return self._method

    @method.setter
    def method(self, new_method: PaymentMethod):
        self._method = new_method

A short explanation is needed:

  1. The webshop holds a reference to its payment method, which we can set by using the the @property syntax
  2. In the Checkout() method we first print out a message, then call the Pay() method on the paymentMethod

The second shop is similar:

class WebshopB(Webshop):
    _method: PaymentMethod = None

    def __init__(self, new_method: PaymentMethod):
        self._method = new_method

    def check_out(self):
        print("Starting checkout for webshop B")
        self._method.pay()

    @property
    def method(self) -> PaymentMethod:
        return self._method

    @method.setter
    def method(self, new_method: PaymentMethod):
        self._method = new_method

Testing time

We can now test our code:

if __name__ == "__main__":
    cash: PaymentMethod = Cash()
    creditcard: PaymentMethod = CreditCard()

    first_webshop: WebshopA = WebshopA(cash)
    first_webshop.check_out()
    first_webshop.method = creditcard
    first_webshop.check_out()

    second_webshop: WebshopB = WebshopB(cash)
    second_webshop.check_out()
    second_webshop.method = creditcard
    second_webshop.check_out()

Line by line:

  1. We construct our two payment-methods
  2. Next we create a WebshopA class with a default method cash, and we checkout
  3. We change the payment method to creditcard and checkout
  4. We repeat the same process for WebshopB

As you can see, by using interface we change the implementation of PaymentMethod without affecting our webshops.

Conclusion

The Bridge pattern reduces the coupling between classes. It does this by separating the implementation from the abstraction. That means either can developed independently without interfering with each other.This makes our code more flexible, more readable and less likely to develop errors due to the changes we make.

As you can see, implementing this pattern in Python is quite easy, and even though we are dealing with two hierarchies, the code remains clear and maintanable: it would for example be quite easy to add another payment method.

In a later article I might want to add generics to the mix, to see how flexible this pattern can get.

Leave a Reply

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