Python: Abstract classes and its practical usage
Using an abstract base class to implement the open-closed principle
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!