Python: Abstract classes and its practical usage

Python: Abstract classes and its practical usage

Using an abstract base class to implement the open-closed principle

·

2 min read

Abstract classes provide a way to define interfaces that act as a blueprint for other classes. These classes can have one or more abstract methods i.e. methods which are declared but not implemented. In general, abstract classes help us minimize repetition and enforce consistency.

Let's use this concept to achieve the open-closed principle which is part of the SOLID design principles. Take for example, that we have a class shape that calculates the area for multiple types of shapes.

from math import pi

class Shape:
    def __init__(self, type, **kwargs):
        self.type = type
        if type == "circle":
            self.radius = kwargs["radius"]
        elif type == "rectangle":
            self.length = kwargs["length"]
            self.breadth = kwargs["breadth"]

    def calculate_area(self):
        if self.type == "circle":
            return pi * self.radius * self.radius
        elif self.type == "rectangle":
            return self.length * self.breadth

Let's test the implementation:

shape = Shape("circle", radius=10)
print(shape.calculate_area()) # Output: 314.1592653589793

It works, but notice the multiple if statements in both the constructor and calculate_area() methods. If you want to add another shape, let's say trapezium, you would have to add another elif block in both the constructor and calculate_area() method. This violates the open-closed principle which states that a class should be open for extension and closed for modification.

Hence, let's rewrite using the abstract base class. We use the abc module to create an abstract class.

from abc import ABC, abstractmethod
from math import pi

class Shape(ABC):
    def __init__(self, type):
        self.type = type

    @abstractmethod
    def calculate_area():
        pass

class Circle(Shape):
    def __init__(self, radius):
        super().__init__("circle")
        self.radius = radius

    # overriding the abstract class method
    def calculate_area(self):
        return pi * self.radius * self.radius

class Rectangle(Shape):
    def __init__(self, length, breadth):
        super().__init__("rectangle")
        self.length = length
        self.breadth = breadth

    # overriding the abstract class method
    def calculate_area(self):
        return 0.5 * self.length * self.breadth

The above implementation makes the Shape class adhere to the open-closed principle. If we want to add a new shape, we can create a new class and inherit it from the base Shape class. Also, note the following:

  • We cannot create an object of the Shape class since it contains an abstract method.

  • Shape class compels any class which derives from it to implement abstract methods.

Now let's test the above implementation. As expected, we get an error if we try to instantiate Shape class:


shape = Shape("Rectangle")  # throws a TypeError: Can't instantiate abstract class Shape with abstract method calculate_area

Using the Rectangle class works correctly and prints the area as required:

shape = Rectangle(5, 10)
print(shape.calculate_area()) # Output: 25.0

Hope this was useful!

References: