Easy Patterns in Go: 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 Go

We will start with the preliminaries:

package main

import "fmt"

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 interface:

type PaymentMethod interface {
	Pay()
}

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

Next we define the CreditCard payment method:

type CreditCard struct {
}

func (c *CreditCard) Pay() {
	fmt.Println("Paying with credit card")
}

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

The Cash payment method is similar:

type Cash struct {
}

func (c *Cash) Pay() {
	fmt.Println("Paying cash")
}

Now we need to define the Webshop interface:

type Webshop interface {
	Checkout()
	SetpaymentMethod(method PaymentMethod)
}

A short explanation:

  1. The Checkout() method is where the Pay() method to the PaymentMethod class gets called
  2. In the SetpaymentMethod() we set the current method of payment.

Now we define our first shop:

type WebshopA struct {
	paymentMethod PaymentMethod
}

func (ws *WebshopA) SetpaymentMethod(method PaymentMethod) {
	ws.paymentMethod = method
}

func (ws *WebshopA) Checkout() {
	fmt.Println("Starting checkout for webshop A")
	ws.paymentMethod.Pay()
}

A short explanation is needed:

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

The second shop is similar:

type WebshopB struct {
	paymentMethod PaymentMethod
}

func (ws *WebshopB) SetpaymentMethod(method PaymentMethod) {
	ws.paymentMethod = method
}

func (ws *WebshopB) Checkout() {
	fmt.Println("Starting checkout for webshop B")
	ws.paymentMethod.Pay()
}

Time to test

Now it is time to test our code:

func main() {
	cash := &Cash{}
	creditCard := &CreditCard{}

	firstWebshop := &WebshopA{paymentMethod: cash}
	firstWebshop.Checkout()
	fmt.Println()

	firstWebshop.SetpaymentMethod(creditCard)
	firstWebshop.Checkout()
	fmt.Println()

	secondWebshop := &WebshopB{paymentMethod: cash}
	secondWebshop.Checkout()
	fmt.Println()

	secondWebshop.SetpaymentMethod(creditCard)
	secondWebshop.Checkout()
	fmt.Println()
}

Line by line:

  1. We construct our two payment-methods
  2. Next we create a WebshopA struct 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 Go 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 *