Pluralsight Notes — Bridge Design Pattern

dp
3 min readJul 2, 2023

--

Notes from watching ‘C# Design Patterns: Bridge’ from Pluralsight. Transformed examples into Python.

Sample Application

from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime

@dataclass
class MovieLicense(ABC):
movie: str
purchase_time: datetime

@abstractmethod
def get_price(self) -> float:
pass

@abstractmethod
def get_expiration_date(self) -> datetime:
pass

class TwoDaysLicense(MovieLicense):
pass

class LifeLongLicense(MovieLicense):
pass

## ...

now = datetime.now()

lic1 = TwoDaysLicense('Secret Life of Pets', now)
lic2 = LifeLongLicense('Matrix', now)

New Requirement(s)

  • add military discount — 10%
  • add senior discount — 20%

naive approach: add two new subclasses to both TwoDaysLicense and LifeLongLicense (6 classes).

This leads to exponential growth of complexity.

Domain knowledge duplication where the same discount is being applied in different spots (don’t repeat yourself)

How to Fix? — Attempt 1

  • split the hierarchy … bridge (acts as a link / composition) to new logic.
@dataclass
class Discount(ABC):
@abstractmethod
def get_discount(self) -> int:
## return percentage -- 20 == .20 %
pass

## using the Null Object Pattern
class NoDiscount(Discount):
def get_discount(self) -> int:
return 0

class MilitaryDiscount(Discount):
def get_discount(self) -> int:
return 10

class SeniorDiscount(Discount):
def get_discount(self) -> int:
return 20

@dataclass
class MovieLicense(ABC):
movie: str
purchase_time: datetime

## Composition...
discount: Discount

def get_price(self) -> float:
return self.get_price_core() * (1 - (self.get_discount() / 100.0))

@abstractmethod
def get_price_core(self) -> float:
pass

@abstractmethod
def get_expiration_date(self) -> datetime:
pass

## ...

now = datetime.now()

lic1 = TwoDaysLicense('Secret Life of Pets', now, NoDiscount())
sen1 = TwoDaysLicense('Secret Life of Pets', now, SeniorDiscount())
milt1 = TwoDaysLicense('Secret Life of Pets', now, MilitaryDiscount())

What did we achieve?

  • no more duplication of discount rules
  • simplified the code

2. How to Fix? — Attempt 2

from enum import Enum

class DiscountType(Enum):
Nothing = 0
Military = 1
Senior = 2

class LicenseType(Enum):
TwoDays = 0
LifeLong = 1

@dataclass
class MovieLicense():
movie: str
purchase_time: datetime
license: LicenseType
discount: DiscountType

def get_price(self) -> float:
return self.get_price_core() * (1 - (self.get_discount() / 100.0))

def get_discount(self) -> int:
if self.discount == DiscountType.Military:
return 10
if self.discount == DiscountType.Senior:
return 20
return 0

def get_price_core(self) -> float:
if self.license == LicenseType.TwoDays:
return 4
if self.discount == LicenseType.LifeLong:
return 8
raise ...

def get_expiration_date(self) -> datetime:
if self.license == LicenseType.TwoDays:
return ...
if self.discount == LicenseType.LifeLong:
return ...
raise ...

## ...

now = datetime.now()

lic1 = MovieLicense(
'Secret Life of Pets',
now,
LicenseType.TwoDay,
DicountType.None
)
  • two hierarchies are now enumerations
  • all logic is in the single base class
  • To extend, just inject a new enumeration (ie. special offers?)

Summary

The Bridge Pattern replaces complexity multiplication with complexity addition (Composition). Its purpose is to split a class hierarchy through composition to reduce coupling — (author)

  • Complexity here is based on coupling.
  • Coupling meaning connections between elements.
  • In the naïve implementation, 2 licenses connected to 3 discounts resulting in 6 connections.

Main takeaway — Composition is more flexible, easier to change, easier to understand…

Photo by kyler trautner on Unsplash

--

--