Skip to content

Design Patterns: Bridge

Published: at 03:09 PM

Formal definition from the Gang of Four:

“it’s purpose is to decouple an abstraction from its implementation, so that the two can vary independently”

Code smells for this pattern

Example

Say you’re building a ticket system with two types of tickets

«abstract»
Ticket
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
DayPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
MonthPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()

You now have new requirements you need to add discounts for students and seniors. A naive way to approach this is to simply add subclasses to each type of ticket, as follows:

«abstract»
Ticket
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
DayPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
MonthPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
StudentDayPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
SeniorDayPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
StudentMonthPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()
SeniorMonthPass
TrainId
PurchaseTime
GetPrice()
GetExpirationDate()

Where in the student and senior subclasses we simply override the GetPrice() methods to apply the discount.

The problem

The new requirement has meant we have added four more classes to the existing hierarchy. This presents a problem because if we wanted to add further discounts say we would have to add even more subclasses. We began with 2, and now it’s 6. If we had another requirement, using this approach we’d have to add another say 3 classes to each subclass. We’d then have 18 classes in total. As you can see these numbers are getting exponentially bigger. This exponential growth of complexity is exactly what we want to avoid.

Another problem is the duplication of business logic. In the StudentDayPass and StudentMonthPass classes the overridden GetPrice() method would be exactly the same; applying the exact same discount. You’d have the same duplication for the senior discount classes. This is a clear code smell since we are not obeying the DRY principle.

The Bridge pattern

The bridge pattern tells use we should split the hierarchy into two.

To do this we are going to move all discount related functionality to a different hierarchy.

«abstract»
Discount
GetDiscount()
NoDiscount
GetDiscount()
StudentDiscount
GetDiscount()
SeniorDiscount
GetDiscount()

We now add discount as a argument for the abstract class Ticket.

We can now refactor the Ticket class a little. We rename GetPrice() to GetPriceCore() which only gets the original price of the ticket. We then write a new GetPrice() method will calculate the discounted price using GetPriceCore() and the _discount property. Since GetPriceCore() is only used by derived classes of Ticket it means we can set it to protected instead of private. Also note that GetPrice() and GetExpirationDate() are abstract methods.

«abstract»
Ticket
-Discount _discount
+Guid TrainId
+DateTime PurchaseTime
#GetPrice() : decimal
+GetExpirationDate() : DateTime

This now gives us the following stucture:

Bridge
«abstract»
Ticket
+Guid TrainId
+DateTime PurchaseTime
-Discount _discount
GetPriceCore() : decimal
GetPrice() : decimal
GetExpirationDate() : DateTime
DayPass
PurchaseTime
GetPriceCore()
GetPrice()
GetExpirationDate()
MonthPass
PurchaseTime
GetPriceCore()
GetPrice()
GetExpirationDate()
«abstract»
Discount
GetDiscount()
NoDiscount
GetDiscount()
StudentDiscount
GetDiscount()
SeniorDiscount
GetDiscount()

Note that the arrow between Ticket and Discount is different; it is the arrow for composition. This is what acts as the “bridge” between the two hierarchies.

If we look at the structure we have now. First thing to note is that we no longer violate the DRY principle. All the business logic to calculate the discounts are found in one place - the derived classes of discount - instead of it being repeated multiple times like before.

Also, we can see that this structure, and by extension the code that follows from this, would be much simpler. We have 5 concrete classes as opposed to the 6 we had before. But more than this, we can now add more types of discount without exponentially increasing the total number of classes. Say for example we had 5 licenses and 5 discounts using our previous approach we would have gotten

5 licenses * 5 discounts = 25 concrete classes.

But using the bridge pattern:

5 licenses + 5 discounts = 10 concrete classes.

We can clearly see that the bridge pattern drastically reduces the number of concrete classes required.

This is the crux of this pattern, it replaces complexity multiplication with complexity addition. Thus it brings under control the expoential growth of complexity.

Recap

Complexity is a function of coupling, where by coupling we mean the number of connection between elements in your code.

The larger the number of connections there are, the greater the complexity of your code.

We can then define the bridge pattern in another way:

It’s purpose is to split a class hierarchy through composition to reduce coupling.

Alternative implementation of the Bridge Pattern

Although we have refactored in the example above we don’t need to stop there. There is still some code duplication that we can refactor out, and we can do this by getting rid of the inheritance and refactoring towards composition.

First, note how in each concrete discount class essentially does the same thing. What we can do here is instead is make discount an enumeration over all the different types of discount. Then move the logic of the discount into a GetDiscount() method in Ticket. In this method you could simply have a switch statement that based on the value of _discount would decide what discount to give.

Bridge
«abstract»
Ticket
+Guid TrainId
+DateTime PurchaseTime
GetPriceCore()
+GetPrice()
+GetExpirationDate()
-GetDiscount()
DayPass
PurchaseTime
GetPriceCore()
GetPrice()
GetExpirationDate()
MonthPass
PurchaseTime
GetPriceCore()
GetPrice()
GetExpirationDate()
«enumeration»
Discount
None
Student
Senior

We dont have to stop here though. You can see that DayPass and MonthPass are also very similar. This means we can convert them to a enum too.

We would also have to do what we did before, we need to inject this enum into the Ticket constructor as _ticketType.

Further we can now remove GetPrice() and replace it with GetBasePrice() to contain the business logic for the ticket prices. It would simply contain a switch statement containing all the prices based on the value of _ticketType. We can also refactor GetExpirationDate to check the value of _licenseType and then have a switch statement return the correct date (making use of PurchaseDate). e.g. for a DayPass we simply have to do PurchaseDate + 1. For a month, PurchaseDate + 30.

Doing this then allows us to get rid of the concrete classes for DayPass and MonthPass

«abstract»
Ticket
+Guid TrainId
+Guid PurchaseTime
-Discount _discount
-TicketType _ticketType
-GetPriceCore()
+GetPrice()
+GetExpirationDate()
-GetDiscount()
«enumeration»
TicketType
DayPass
MonthPass
«enumeration»
Discount
None
Student
Senior

Summary

Using the bridge pattern we have done the following: