7.6. Bridge

  • EN: Bridge

  • PL: Most

  • Type: object

The Bridge design pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. This pattern involves an interface which acts as a bridge which makes the functionality of concrete classes independent from interface implementer classes.

Here's a simple example of the Bridge pattern in Python:

>>> class Abstraction:
...     def __init__(self, implementation):
...         self._implementation = implementation
...
...     def operation(self):
...         return (f"Abstraction: Base operation with:\n"
...                 f"{self._implementation.operation_implementation()}")
...
>>> class Implementation:
...     def operation_implementation(self):
...         pass
...
>>> class ConcreteImplementationA(Implementation):
...     def operation_implementation(self):
...         return "ConcreteImplementationA: Here's the result on the platform A."
...
>>> class ConcreteImplementationB(Implementation):
...     def operation_implementation(self):
...         return "ConcreteImplementationB: Here's the result on the platform B."
...
>>> implementation = ConcreteImplementationA()
>>> abstraction = Abstraction(implementation)
>>> print(abstraction.operation())
Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A.
>>> implementation = ConcreteImplementationB()
>>> abstraction = Abstraction(implementation)
>>> print(abstraction.operation())
Abstraction: Base operation with:
ConcreteImplementationB: Here's the result on the platform B.

In this example, Abstraction is a class that maintains a reference to an object of the Implementation class. It defines an interface and maintains the link between it and the objects of the Implementation class. ConcreteImplementationA and ConcreteImplementationB are concrete classes that implement the Implementation interface. The operation method in the Abstraction class calls the operation_implementation method in the Implementation class.

7.6.1. Pattern

  • Nested hierarchies of classes

design-patterns/structural/img/designpatterns-bridge-pattern.png

7.6.2. Problem

  • Every time you want to add new manufacturers remote control, you need to add two classes of ManufacturerRemoteControl and ManufacturerAdvancedRemoteControl

  • If you add new another type of remote control, such as MovieRemoteControl you now has to implement three classes

Basic Remote Control (turnOn, turnOff)
Advanced Remote Control (setChannel)
Movie Remote Control (play, pause, rewind)
RemoteControl
    SonyRemoteControl
    SamsungRemoteControl
    AdvancedRemoteControl
        SonyAdvancedRemoteControl
        SamsungAdvancedRemoteControl
../../_images/designpatterns-bridge-problem.png

from abc import ABC, abstractmethod


class RemoteControl(ABC):
    @abstractmethod
    def turn_on(self) -> None:
        pass

    @abstractmethod
    def turn_off(self) -> None:
        pass


class AdvancedRemoteControl(RemoteControl, ABC):
    @abstractmethod
    def set_channel(self, number: int) -> None:
        pass

class SonyRemoteControl(RemoteControl):
    def turn_off(self) -> None:
        print('Sony turn off')

    def turn_on(self) -> None:
        print('Sony turn on')


class SonyAdvancedRemoteControl(AdvancedRemoteControl):
    def set_channel(self, number: int) -> None:
        print('Sony set channel')

    def turn_off(self) -> None:
        print('Sony turn off')

    def turn_on(self) -> None:
        print('Sony turn on')

7.6.3. Solution

  • Hierarchy grows in two different directions

  • We can split complex hierarchy into two hierarchies which grows independently

../../_images/designpatterns-bridge-solution-1.png
../../_images/designpatterns-bridge-solution-2.png

from abc import ABC, abstractmethod
from dataclasses import dataclass


class Device(ABC):
    @abstractmethod
    def set_channel(self, number: int) -> None:
        pass

    @abstractmethod
    def turn_off(self) -> None:
        pass

    @abstractmethod
    def turn_on(self) -> None:
        pass


@dataclass
class RemoteControl:
    device: Device

    def turn_on(self) -> None:
        self.device.turn_on()

    def turn_off(self) -> None:
        self.device.turn_off()


class AdvancedRemoteControl(RemoteControl):
    def set_channel(self, number: int) -> None:
        self.device.set_channel(number)


class SonyTV(Device):
    def set_channel(self, number: int) -> None:
        print('Sony set channel')

    def turn_off(self) -> None:
        print('Sony turn off')

    def turn_on(self) -> None:
        print('Sony turn on')


class SamsungTV(Device):
    def set_channel(self, number: int) -> None:
        print('Samsung set channel')

    def turn_off(self) -> None:
        print('Samsung turn off')

    def turn_on(self) -> None:
        print('Samsung turn on')


if __name__ == '__main__':
    remote_control = RemoteControl(SonyTV())
    remote_control.turn_on()

    remote_control = AdvancedRemoteControl(SonyTV())
    remote_control.turn_on()

    remote_control = RemoteControl(SamsungTV())
    remote_control.turn_on()

7.6.4. Assignments