Skip to content

Bridge Design Pattern

Video Lecture

Section Video Links
Bridge Overview Bridge Overview Bridge Overview 
Bridge Use Case Bridge Use Case Bridge Use Case 
Python Tuple Python Tuple Python Tuple 
Python *args Python *args Python *args 

Overview

The Bridge pattern is similar to the Adapter pattern except in the intent that you developed it.

The Bridge is an approach to refactor already existing code, whereas the Adapter creates an interface on top of existing code through existing available means without refactoring any existing code or interfaces.

The motivation for converting your code to the Bridge pattern is that it may be tightly coupled. There is logic and abstraction close together that is limiting your choices in how you can extend your solution in the way that you need.

E.g., you may have one Car class, that produces a very nice car. But you would like the option of varying the design a little, or outsourcing responsibility of creating the different components.

The Bridge pattern is a process about separating abstraction and implementation, so this will give you plenty of new ways of using your classes.

CAR = Car()
print(CAR)
> Car has wheels and engine and windows and everything else.

But you would like to delegate the engine dynamically from a separate set of classes or solutions.

ENGINE = EngineA()
CAR = Car(EngineA)

A Bridge didn't exist before, but since after the separation of interface and logic, each side can be extended independently of each other.

Also, the application of a Bridge in your code should use composition instead of inheritance. This means that you assign the relationship at runtime, rather than hard coded in the class definition.

I.e., CAR = Car(EngineA) rather than class Car(EngineA):

A Bridge implementation will generally be cleaner than an Adapter solution that was bolted on. Since it involved refactoring existing code, rather than layering on top of legacy or third-party solutions that may not have been intended for your particular use case.

You are the designer of the Bridge, but both approaches to the problem may work regardless.

The implementer part of a Bridge, can have one or more possible implementations for each refined abstraction. E.g., The implementor can print to paper, or screen, or format for a web browser. And the abstraction side could allow for many permutations of every shape that you can imagine.

Terminology

  • Abstraction Interface: An interface implemented by the refined abstraction describing the common methods to implement.
  • Refined Abstraction: A refinement of an idea into another class or two. The classes should implement the Abstraction Interface and assign which concrete implementer.
  • Implementer Interface: The implementer interface that concrete implementers implement.
  • Concrete Implementer: The implementation logic that the refined abstraction will use.

Bridge UML Diagram

Bridge Pattern UML Diagram

Source Code

In the concept demonstration code, imagine that the classes were tightly coupled. The concrete class would print out some text to the console.

After abstracting the class along a common ground, it is now more versatile. The implementation has been separated from the abstraction, and now it can print out the same text in two different ways.

The benefit now is that each refined abstraction and implementer can now be worked on independently without affecting the other implementations.

./bridge/bridge_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# pylint: disable=too-few-public-methods
# pylint: disable=arguments-differ
"Bridge Pattern Concept Sample Code"
from abc import ABCMeta, abstractmethod

class IAbstraction(metaclass=ABCMeta):
    "The Abstraction Interface"

    @staticmethod
    @abstractmethod
    def method(*args):
        "The method handle"

class RefinedAbstractionA(IAbstraction):
    "A Refined Abstraction"

    def __init__(self, implementer):
        self.implementer = implementer()

    def method(self, *args):
        self.implementer.method(*args)

class RefinedAbstractionB(IAbstraction):
    "A Refined Abstraction"

    def __init__(self, implementer):
        self.implementer = implementer()

    def method(self, *args):
        self.implementer.method(*args)

class IImplementer(metaclass=ABCMeta):
    "The Implementer Interface"

    @staticmethod
    @abstractmethod
    def method(*args: tuple) -> None:
        "The method implementation"

class ConcreteImplementerA(IImplementer):
    "A Concrete Implementer"

    @staticmethod
    def method(*args: tuple) -> None:
        print(args)

class ConcreteImplementerB(IImplementer):
    "A Concrete Implementer"

    @staticmethod
    def method(*args: tuple) -> None:
        for arg in args:
            print(arg)

# The Client
REFINED_ABSTRACTION_A = RefinedAbstractionA(ConcreteImplementerA)
REFINED_ABSTRACTION_A.method('a', 'b', 'c')

REFINED_ABSTRACTION_B = RefinedAbstractionB(ConcreteImplementerB)
REFINED_ABSTRACTION_B.method('a', 'b', 'c')

Output

python ./bridge/bridge_concept.py
('a', 'b', 'c')
a
b
c

SBCODE Editor

<>

Example Use Case

In this example, I draw a square and a circle. Both of these can be categorized as shapes.

The shape is set up as the abstraction interface. The refined abstractions, Square and Circle, implement the IShape interface.

When the Square and Circle objects are created, they are also assigned their appropriate implementers being SquareImplementer and CircleImplementer.

When each shape's draw method is called, the equivalent method within their implementer is called.

The Square and Circle are bridged and each implementer and abstraction can be worked on independently.

Example UML Diagram

Bridge Pattern in Context

Source Code

./bridge/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"Bridge Pattern Use Case Example"

from circle_implementer import CircleImplementer
from square_implementer import SquareImplementer
from circle import Circle
from square import Square

CIRCLE = Circle(CircleImplementer)
CIRCLE.draw()

SQUARE = Square(SquareImplementer)
SQUARE.draw()

./bridge/circle_implementer.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pylint: disable=too-few-public-methods
"A Circle Implementer"
from interface_shape_implementer import IShapeImplementer

class CircleImplementer(IShapeImplementer):
    "A Circle Implementer"

    def draw_implementation(self):
        print("    ******")
        print("  **      **")
        print(" *          *")
        print("*            *")
        print("*            *")
        print(" *          *")
        print("  **      **")
        print("    ******")

./bridge/square_implementer.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pylint: disable=too-few-public-methods
"A Square Implementer"
from interface_shape_implementer import IShapeImplementer

class SquareImplementer(IShapeImplementer):
    "A Square Implementer"

    def draw_implementation(self):
        print("**************")
        print("*            *")
        print("*            *")
        print("*            *")
        print("*            *")
        print("*            *")
        print("*            *")
        print("**************")

./bridge/circle.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pylint: disable=too-few-public-methods
"A Circle Abstraction"
from interface_shape import IShape

class Circle(IShape):
    "The Circle is a Refined Abstraction"

    def __init__(self, implementer):
        self.implementer = implementer()

    def draw(self):
        self.implementer.draw_implementation()

./bridge/square.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pylint: disable=too-few-public-methods
"A Square Abstraction"
from interface_shape import IShape

class Square(IShape):
    "The Square is a Refined Abstraction"

    def __init__(self, implementer):
        self.implementer = implementer()

    def draw(self):
        self.implementer.draw_implementation()

./bridge/interface_shape_implementer.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# pylint: disable=too-few-public-methods
"A Shape Implementor Interface"
from abc import ABCMeta, abstractmethod

class IShapeImplementer(metaclass=ABCMeta):
    "Shape Implementer"

    @staticmethod
    @abstractmethod
    def draw_implementation():
        "The method that the refined abstractions will implement"

./bridge/interface_shape.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# pylint: disable=too-few-public-methods
"The Shape Abstraction Interface"
from abc import ABCMeta, abstractmethod

class IShape(metaclass=ABCMeta):
    "The Shape Abstraction Interface"

    @staticmethod
    @abstractmethod
    def draw():
        "The method that will be handled at the shapes implementer"

Output

python ./bridge/client.py
    ******
  **      **
 *          *
*            *
*            *
 *          *
  **      **
    ******
**************
*            *
*            *
*            *
*            *
*            *
*            *
**************

SBCODE Editor

<>

New Coding Concepts

The *args Argument.

The *args argument takes all arguments that were sent to this method, and packs them into a Tuple.

It is useful when you don't know how many arguments, or what types, will be sent to a method, and you want your method to be able support any number of arguments and types being sent to it.

If you want your method to be strict about what types that it can accept, then set it specifically to accept a List, Dictionary, Set or Tuple, and treat the argument as such within the method body, but the *args argument is another common option that you will see throughout source code examples on the internet.

E.g., when using the *args in your method signature, you can call it with any number of arguments of any type.

1
2
3
4
5
6
7
8
def my_method(*args):

    print(type(args))

    for arg in args:
        print(arg)

my_method(1, 22, [3], {4})

Outputs

<class 'tuple'>
1
22
[3]
{4}

Python Tuple

A Python Tuple is similar to a List. Except that the items in the Tuple are ordered, unchangeable and allow duplicates.

A Tuple can be instantiated using the round brackets () or tuple(), verses [] for a List and {} for a Set or Dictionary.

PS> python
>>> items = ("alpha", "bravo", "charlie", "alpha")
>>> print(items)
('alpha', 'bravo', 'charlie', 'alpha')
>>> print(len(items))
4

Summary

  • Use when you want to separate a solution where the abstraction and implementation may be tightly coupled, and you want to break it up into smaller conceptual parts.
  • Once you have added the bridge abstraction, you should be able to extend each side of it separately without breaking the other.
  • Also, once the bridge abstraction exists, you can more easily create extra concrete implementations for other similar products that may also happen to be split across similar conceptual lines.
  • The Bridge pattern is similar to the adapter pattern except in the intent that you developed it. The bridge is an approach to refactor already existing code, whereas the adapter adapts to the existing code through its existing interfaces and methods without changing the internals.