Bridge

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 2 minutes

Bridge Design Pattern in Python

Bridge Design Pattern in Python

Design Patterns Home
 

What is it?

The Bridge Design Pattern is adopted when implementation-specific classes are mixed together with implementation-independent classes.

It is classified under Structural Design Patterns as it offers one of the best methods to organize class hierarchy.

Consider the following class Circle, which has three attributes: radius, x & y coordinates of its center, and 3 methods: drawWithAPIOne(), drawWithAPITwo() and scale(). Of these, the drawing methods are implementation specific as we have two APIs classes inside the Circle class to draw a circle, and it depends on the user which one to use. This is represented by the following code:

# BEFORE BRIDGE PATTERN
# We have a Circle class having 3 attributes and 3 methods.
# Attributes are radius, coordinates on a 2-D plane.
# Methods are drawWithAPIOne(), drawWithAPITwo() and scale(). Of these, the drawing methods are implementation specific as we have two APIs to draw a circle, and it depends on the user which one to use.

class Circle:
        class DrawingAPIOne:
                '''Implementation-specific abstraction'''
                def drawCircle(self, x, y, radius):
                        print("API 1 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))
        class DrawingAPITwo:
                '''Implementation-specific abstraction'''
                def drawCircle(self, x, y, radius):
                        print("API 2 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))
                        
        def __init__(self, x, y, radius):
                '''Implementation-independent abstraction; Initialize the necessary attributes'''
                self._x = x
                self._y = y
                self._radius = radius
        def drawWithAPIOne(self):
                '''Implementation-specific abstraction'''
                objectOfAPIone = self.DrawingAPIOne()
                objectOfAPIone.drawCircle(self._x, self._y, self._radius)
        def drawWithAPITwo(self):
                '''Implementation-specific abstraction'''
                objectOfAPItwo = self.DrawingAPITwo()
                objectOfAPItwo.drawCircle(self._x, self._y, self._radius)
        def scale(self, percent):
                '''Implementation-independent abstraction'''
                self._radius *= percent

# Instantiate a circle
circle1 = Circle(0, 0, 2)
# Draw it using API One
circle1.drawWithAPIOne()

# Instantiate another circle
circle2 = Circle(1, 3, 3)
# Draw it using API Two
circle2.drawWithAPITwo()

## OUTPUT ##
API 1 is drawing a circle at (0, 0) with radius 2
API 2 is drawing a circle at (1, 3) with radius 3

When the Bridge Pattern is in effect, it resolves this complicated class hierarchy. It separates the implementation-independent class(es) from the implementation-specific class(es).


Why the need for it: Problem Statement

To segregate implementation-specific classes from implementation-independent classes, to facilitate cleaner design.


Terminology

  • Implementation-independent abstraction: functionality that is most likely to remain constant in all client requests.
  • Implementation-specific abstraction: functionality that is chosen in a particular client request.

Pseduo Code

class DrawingAPIOne:
        '''Implementation-specific abstraction'''
        def drawCircle(x, y, radius):
                draws a circle using arguments provided

class DrawingAPITwo:
        '''Implementation-specific abstraction'''
        def drawCircle(x, y, radius):
                draws a circle using arguments provided

class Circle:                        
        def __init__(self, x, y, radius, drawingAPI):
                '''Implementation-independent abstraction; Initialize the necessary attributes'''
                assigns supplied arguments to local variables
        def draw(self):
                '''Implementation-specific abstraction'''
                calls the drawCircle() method of the drawingAPI object provided as argument.
        def scale(self, percent):
                '''Implementation-independent abstraction'''
                increases the radius of the circle by a given percentage

### USING THE ABOVE CODE ###
Instantiate a circle and pass to it an object of DrawingAPIOne in its 4rth argument
Draw it using API One
Instantiate another circle and pass to it an object of DrawingAPITwo in its 4rth argument
Draw it using API Two

How to implement it ??

# We have a Circle class having 3 attributes and 3 methods.
# Attributes are radius, coordinates on a 2-D plane.
# Methods are drawWithAPIOne(), drawWithAPITwo() and scale(). Of these, the drawing methods are implementation specific as we have two APIs to draw a circle, and it depends on the user which one to use.
# Our purpose here is to separate the Implementation Specific Abstraction and Implementaion Independent Abstraction into two different class hierarchies.
# We wish to keep the Circle class confined to defining its properties and implementation-independent scale() method AND have the draw functionality (which is implementation specific)
# delegated to a separate class hierarchy.

class DrawingAPIOne:
        '''Implementation-specific abstraction'''
        def drawCircle(self, x, y, radius):
                print("API 1 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))

class DrawingAPITwo:
        '''Implementation-specific abstraction'''
        def drawCircle(self, x, y, radius):
                print("API 2 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))

class Circle:                        
        def __init__(self, x, y, radius, drawingAPI):
                '''Implementation-independent abstraction; Initialize the necessary attributes'''
                self._x = x
                self._y = y
                self._radius = radius
                self._drawingAPI = drawingAPI
        def draw(self):
                '''Implementation-specific abstraction'''
                self._drawingAPI.drawCircle(self._x, self._y, self._radius)
        def scale(self, percent):
                '''Implementation-independent abstraction'''
                self._radius *= percent

# Instantiate a circle and pass to it an object of DrawingAPIOne in its 4rth argument
circle1 = Circle(0, 0, 2, DrawingAPIOne())
# Draw it using API One
circle1.draw()

# Instantiate another circle and pass to it an object of DrawingAPITwo in its 4rth argument
circle2 = Circle(1, 3, 3, DrawingAPITwo())
# Draw it using API Two
circle2.draw()

Walkthrough of implementation

  1. First, we create a circle object with x = 0, y = 0, radius = 2 and drawingAPI = DrawingAPIOne().
  2. We then call its draw() method, which calls the drawCircle() method of the passed DrawingAPIOne object.
  3. Then, to test the functionality of the second drawing API, we create another circle object, with x = 1, y = 3, radius = 3 & drawingAPI = DrawingAPITwo().
  4. We then call its draw() method, which calls the drawCircle() method of the passed DrawingAPITwo object.

Comparison of code when it is implemented and when it is not

BEFORE BRIDGE PATTERN:

# We have a Circle class having 3 attributes and 3 methods.
# Attributes are radius, coordinates on a 2-D plane.
# Methods are drawWithAPIOne(), drawWithAPITwo() and scale(). Of these, the drawing methods are implementation specific as we have two APIs to draw a circle, and it depends on the user which one to use.

class Circle:
        class DrawingAPIOne:
                '''Implementation-specific abstraction'''
                def drawCircle(self, x, y, radius):
                        print("API 1 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))
        class DrawingAPITwo:
                '''Implementation-specific abstraction'''
                def drawCircle(self, x, y, radius):
                        print("API 2 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))
                        
        def __init__(self, x, y, radius):
                '''Implementation-independent abstraction; Initialize the necessary attributes'''
                self._x = x
                self._y = y
                self._radius = radius
        def drawWithAPIOne(self):
                '''Implementation-specific abstraction'''
                objectOfAPIone = self.DrawingAPIOne()
                objectOfAPIone.drawCircle(self._x, self._y, self._radius)
        def drawWithAPITwo(self):
                '''Implementation-specific abstraction'''
                objectOfAPItwo = self.DrawingAPITwo()
                objectOfAPItwo.drawCircle(self._x, self._y, self._radius)
        def scale(self, percent):
                '''Implementation-independent abstraction'''
                self._radius *= percent

# Instantiate a circle
circle1 = Circle(0, 0, 2)
# Draw it using API One
circle1.drawWithAPIOne()

# Instantiate another circle
circle2 = Circle(1, 3, 3)
# Draw it using API Two
circle2.drawWithAPITwo()

## OUTPUT ##
API 1 is drawing a circle at (0, 0) with radius 2
API 2 is drawing a circle at (1, 3) with radius 3

AFTER BRIDGE PATTERN:

# We have a Circle class having 3 attributes and 3 methods.
# Attributes are radius, coordinates on a 2-D plane.
# Methods are drawWithAPIOne(), drawWithAPITwo() and scale(). Of these, the drawing methods are implementation specific as we have two APIs to draw a circle, and it depends on the user which one to use.
# Our purpose here is to separate the Implementation Specific Abstraction and Implementaion Independent Abstraction into two different class hierarchies.
# We wish to keep the Circle class confined to defining its properties and implementation-independent scale() method AND have the draw functionality (which is implementation specific)
# delegated to a separate class hierarchy.

class DrawingAPIOne:
        '''Implementation-specific abstraction'''
        def drawCircle(self, x, y, radius):
                print("API 1 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))

class DrawingAPITwo:
        '''Implementation-specific abstraction'''
        def drawCircle(self, x, y, radius):
                print("API 2 is drawing a circle at ({}, {}) with radius {}".format(x, y, radius))

class Circle:                        
        def __init__(self, x, y, radius, drawingAPI):
                '''Implementation-independent abstraction; Initialize the necessary attributes'''
                self._x = x
                self._y = y
                self._radius = radius
                self._drawingAPI = drawingAPI
        def draw(self):
                '''Implementation-specific abstraction'''
                self._drawingAPI.drawCircle(self._x, self._y, self._radius)
        def scale(self, percent):
                '''Implementation-independent abstraction'''
                self._radius *= percent

# Instantiate a circle and pass to it an object of DrawingAPIOne in its 4rth argument
circle1 = Circle(0, 0, 2, DrawingAPIOne())
# Draw it using API One
circle1.draw()

# Instantiate another circle and pass to it an object of DrawingAPITwo in its 4rth argument
circle2 = Circle(1, 3, 3, DrawingAPITwo())
# Draw it using API Two
circle2.draw()

## OUTPUT ##
API 1 is drawing a circle at (0, 0) with radius 2
API 2 is drawing a circle at (1, 3) with radius 3

Related to: Abstract Factory & Adapter


 

 

 

 


See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply