Bridge design pattern in python

Structural design pattern: This design pattern mainly focuses on the relationship between objects and classes and their inheritance. We know that software comprises many objects and classes, so properly organizing them is quite challenging. A structural design pattern helps to organize different classes and objects to form larger functions. It adds multiple functionalities but keeps it flexible and efficient. All the interfaces are created using inheritance. It simplifies the structure of the code. Many classes are assembled to form a significant structure. It is maintainable.

Bridge design pattern :

This design pattern is mainly used in decoupling abstraction, separating self-related classes into two hierarchies abstract and implementation. Abstraction can be altered. Abstraction and implementation can be developed independently of each other. We can separate abstraction from implementation using the bridge method. Here bridge acts as an interface.

Abstraction: The control part of two hierarchies of classes is maintained by an interface, which is an abstraction. A delegation of all real-world work with objects is done by abstraction.

Implementation: The interface is controlled by the implementation, defining an interface for all the available classes is done by the implementation. It doesn't need to match the interface of abstraction.

Problem statement :

Bridge design pattern in python

Suppose we have three geometrical shapes with us square, triangle, and circle so that we will have three different classes for all of them. Now, if we want to add two colors, red and blue, to them, there would be six variations of classes, as shown in the diagram below.

Bridge design pattern in python

Suppose if I add two more shapes here, then the code will become more complex, exception handling will be tough too, and if we add one more color, it will complicate the code again. The software consists of multiple classes and objects. Following this method will be inappropriate and will not be efficient at all. So we need to make software efficient and reliable, so the code complexity should be optimum and dynamic. So let's see how the bridge method can resolve the problem.

Solution statement

The only reliable solution is abstracting the colors and keeping them separate when

any shape needs it, then the bridge method will act as an interface so we can reduce the number of classes easily, and the complexity of the code will be optimum.

Let's try to implement it using practical python code -

Example1.py :

class API1:
def produce_rectangle(self, length_, breadth_, height_):
print(f'API1 is producing rectangle of this dimension = {length_}, '
      f' Breadth = {breadth_} and Height = {height_}')


class API2:
  def produce_rectangle(self, length_, breadth_, height_):
  print(f'API2 is producing rectangle of this dimension = {length_}, '
      f' Breadth = {breadth_} and Height = {height_}')


class rectangle:
def __init__(self, length, breadth, height, producingAPI_):
 self._length = length
    self._breadth = breadth
    self._height = height
self._producingAPI = producingAPI_
 def produce(self):
  self._producingAPI.produce_rectangle(self._length, self._breadth, self._height)
def expand(self, times):
self._length = self._length * times
    self._breadth = self._breadth * times
    self._height = self._height * times
rectangle1 = rectangle(1, 2, 3, API1())
rectangle1.produce()
rectangle3 = rectangle(1, 2, 3, API1())
rectangle3.produce()
rectangle2 = rectangle(19, 19, 19, API2())
rectangle2.produce()
rectangle4 = rectangle(19, 19, 19, API2())
rectangle4.produce()

Output.txt :

API1 is producing rectangle of this dimension = 1,  Breadth = 2 and Height = 3
API1 is producing rectangle of this dimension = 1,  Breadth = 2 and Height = 3
API2 is producing rectangle of this dimension = 19,  Breadth = 19 and Height = 19
API2 is producing rectangle of this dimension = 19,  Breadth = 19 and Height = 19

Explanation :

Here the code is implemented with the bridge method. We have a rectangle that contains three properties named length, breadth, and height and three methods named API1, API2, and expand. We aim to separate the implementation-specific abstraction from the available implementation-independent abstraction.

Creation and implementation of specific abstraction :

Code:

def produce_rectangle(self, length_, breadth_, height_):
print(f'API1 is producing rectangle of this dimension = {length_}, '
      f' Breadth = {breadth_} and Height = {height_}')

Here we have defined the object that is produce_rectangle and from here we are producing rectangles here, and each rectangle has three properties, as mentioned. Length, breadth, and height. These three values will be passed here whenever this function is called, and then it will display the statement which is inside the print statement. We will abstract specific properties that we need.API2 also has the same producing class with identical code, which works similarly.

Initialization of available necessary attributes

Code:

self._length = self._length * times
    self._breadth = self._breadth * times
    self._height = self._height * times

Explanation: To implement independent abstraction, we need to initialize the attributes' height, length, and breadth. This will help us to make abstraction and implementation independent of each other.

Producing rectangle :

rectangle1 = rectangle(1, 2, 3, API1())
rectangle1.produce()

This is how we can produce the rectangle. We are instantiating the rectangle1  and then passing it to the object of produce_API, and then it is produced.

So just by passing values, we are able to produce our required rectangle and this is one of the simplest examples of bridge design patterns.

Example 2

Example2.py code:

from abc import abstractmethod
class shape1:
    def no_of_sides(self, sides1):
        self.sides = sides1
    def set_color(self, color):
        self.color = color._color   
    @abstractmethod
    def Find_area(self):
        pass    
    
class Square1(shape1):
    def __init__(self, width, sides, color):
        self.width = width
        super().no_of_sides(sides)
        super().set_color(color)


    def Find_area(self):
        return self.width ** 2
    def __str__(self):
        return f'Shape: {type(self).__name__}, Side: {self.sides}, Colour: {self.color}, Area: {self.Find_area()}'


class Circle1(shape1):
    def __init__(self, radius, sides, color):
        self.radius1 = radius
        super().no_of_sides(0)
        super().set_color(color)    


    def Find_area(self):
        return 3.14*self.radius1**2
    
    def __str__(self):
        return f'Shape: {type(self).__name__}, Side: {self.sides}, Colour: {self.color}, Area: {self.Find_area()}'
class triangle(shape1):
                  def __init__(self,sides, color):
  
                   super().no_of_sides(3)
                   super().set_color(color)    


def Find_area(self):
        return 0.433*self.sides*self.sides
    
def __str__(self):
        return f'Shape: {type(self).__name__}, Side: {self.sides}, Colour: {self.color}, Area: {self.Find_area()}'
            
        
"""Interface for implementation"""
class Color:
    def fill_color(self):
        pass
class RedColor(Color):
    def __init__(self):
        self.fill_color()
    def fill_color(self):
        self._color = 'Red'
class greenColor(Color):
    def __init__(self):
        self.fill_color()
    def fill_color(self):
        self._color = 'green'
class blackColor(Color):
    def __init__(self):
        self.fill_color()
def fill_color(self):
self._color = 'black'


class BlueColor(Color):
def __init__(self):
self.fill_color()
def fill_color(self):
self._color = 'Blue'


#Driver Code
objS = Square1(6, 4, RedColor())
objC = Circle1(4, 0, blackColor())
objc2 = Circle1(4, 0, BlueColor())
objT = triangle(3, BlueColor())
print(objS, objC, objT,objc2,sep="n")

Output.txt:

Shape: Square 1, Side: 4, Colour: Red, Area: 36
Shape: Circle1, Side: 0, Colour: black, Area: 50.24
__main__.triangle object at 0x7f63e2ae75d0>
Shape: Circle1, Side: 0, Colour: Blue, Area: 50.24

Explanation :

So here we have three shapes and four available colors. Instead of creating shapes of each color, we have separated the colors and shapes. Whenever any specific shape of any specific color is needed, we will instantiate it during runtime. So let's see the working of code.

class shape1:
def no_of_sides(self, sides1):
self.sides = sides1
set_color(self, color):
self.color = color._color 

Here the shape and its attributes sides and color is defined.we will be passing values for each shape.

Then we have defined the each specific shapes separately.

class Square1(shape1):
    def __init__(self, width, sides, color):
        self.width = width
        super().no_of_sides(sides)
        super().set_color(color)


    def Find_area(self):
        return self.width ** 2
    def __str__(self):
        return f'Shape: {type(self).__name__}, Side: {self.sides}, Colour: {self.color}, Area: {self.Find_area()}'

This is for square. We have defined two attributes the first one is sides, and the second one is color. We will be instantiating them during run time bypassed values. We are also returning the area and have defined a separate function for it. After all the necessary steps, we return the shape, size, color, and area value.

This code pattern is the same for all present shapes and will work similarly.

Defining color:

We have defined four colors red, blue, black, and green. It depends on the requirement. We can easily add any color to the shape. We just need to pass the value to the object while calling.

objS = Square1(6, 4, RedColor())

This is how the instantiation of objects is done. We call the square method and provide arguments such as self, sides, and color, so it prints the desirable result. We can produce any shape with any color by just following this method.

Advantages of using bridge design pattern :

  • The open/close principle is not violated by this method.
    [note-the open/close principle is one of the most important principles of object-oriented programming. It says software entities should be open for extension but must be closed for modification. Simply, it means adding new functionality to existing code without interrupting or changing the existing code and functionality.
  • This design method can also implement platform-independent features, one of the major benefits of using this design pattern.
  • It makes the code less bulky and also enables adding more functionality simple.
  • The single responsibility method is followed here.
  • The unnecessary details are hidden from the client.

Disadvantages of using bridge design pattern :

  • It depends on the number of sets. A plethora of sets with a single implementation will make the code complex, and even implementing it becomes difficult.
  • We have to define new classes here, which might complicate the existing code.
  • The implementation fashion might reduce the overall performance as it includes several steps.