In Part 1, I wrote about the first two principles of SOLID. Part 2 is going to be about the third and fourth principles of SOLID which are the Liskov Substitution Principle and Interface Segregation Principle. Let's start!
Liskov Substitution Principle
The Liskov substitution principle (LSP) is called strong behavioral subtyping, which was initially introduced by Barbara Liskov in a 1987 conference keynote. It states that a class can be replaced by a sub-class without breaking the program. This means that subclasses should be behaving almost the same as their parent class.
Assume that we need to register students in a variety of courses; math, chemistry, physics etc. We could create a superclass RegisterCourse
to define the blueprint for the course registry. Per course, we can create subclasses such as RegisterMath
overrides the register
method per course. But, superclass defines the input argument and a draft behavior. That's why it is called a blueprint.
But assume that, we need to register students also for the Computer Science course. For this course, the requirement is different; GitHub account instead of student_name. We can create classes as following.
Bad Example
from abc import ABC, abstractmethod
class RegisterCourse(ABC):
def register(self, student_name):
pass
class RegisterMath(RegisterCourse):
def register(self, student_name):
print(f"Student with name: {student_name} is registered to math course.")
class RegisterChemistry(RegisterCourse):
def register(self, student_name):
print(f"Student with name: {student_name} is registered to chemistry course.")
class RegisterComputerScience(RegisterCourse):
def register(self, github_account):
print(f"Student with github account: {github_account} is registered to computer science course.")
math_registerer = RegisterMath()
math_registerer.register("Enis")
computerscience_registerer = RegisterComputerScience()
computerscience_registerer.register("enis@github.com")
Is this correct? No. We modified the blueprint given by RegisterCourse
superclass by modifying the input argument type. Registering for a computer science course requires a different input which deserves to be in a different superclass. Let's create a superclass for this; RegisterCourseWithGithubAccount
. The nice thing now is that any computer science related course can be introduced as a subclass of RegisterCourseWithGithubAccount
.
Good Example
from abc import ABC, abstractmethod
class RegisterCourse(ABC):
def register(self, student_name):
pass
class RegisterMath(RegisterCourse):
def register(self, student_name):
print(f"Student with name: {student_name} is registered to math course.")
class RegisterChemistry(RegisterCourse):
def register(self, student_name):
print(f"Student with name: {student_name} is registered to chemistry course.")
class RegisterCourseWithGithubAccount(ABC):
def register(self, github_account):
pass
class RegisterComputerScience(RegisterCourseWithGithubAccount):
def register(self, github_account):
print(f"Student with github account: {github_account} is registered to computer science course.")
math_registerer = RegisterMath()
math_registerer.register("Enis")
computerscience_registerer = RegisterComputerScience()
computerscience_registerer.register("enis@github.com")
This way, we do not change the behavior of the inherited method which is the underlying message in LSP. This was the third principle of SOLID. Now, let's have a look at the fourth principle.
Interface Segregation Principle
The interface Segregation Principle (ISP) states that sub-classes cannot be forced to depend on methods that they do not use. When we see a bulky interface that some of its methods are abundant for the client, then it is a bad smell that this principle is violated. Let's have a look at the following example for this.
Bad Example
In this example, we allocated the courses inside RegisterCourses
class where FirstYearStudent
class can inherit these courses as a subclass. It is the same for SecondYearStudent
with one difference. Second Year students can register for both courses, however, first year students cannot register for the Advanced Algorithms course because it is only open from second year onwards.
from abc import ABC, abstractmethod
class RegisterCourses(ABC):
def RegisterIntroductionToComputerScience(self):
pass
def RegisterAdvancedAlgorithms(self):
pass
class FirstYearStudent(RegisterCourses):
def RegisterIntroductionToComputerScience(self):
print("Register to Introduction to Computer Science Course")
def RegisterAdvancedAlgorithms(self):
raise Exception("Registery to Advanced Algorithms Course is not possible.")
class SecondYearStudent(RegisterCourses):
def RegisterIntroductionToComputerScience(self):
print("Register to Introduction to Computer Science Course")
def RegisterAdvancedAlgorithms(self):
print("Register to Advanced Algorithms Course")
The problem here is that FirstYearStudent class inherits RegisterAdvancedAlgorithms
method which is not in use. Therefore, it needs to handle the method by raising an exception error. This violates the ISP principle. Instead, we can divide the superclass RegisterCourses
into two classes; RegisterBeginnerCourses
and RegisterAdvanceCourses
.
Good Example
from abc import ABC, abstractmethod
class RegisterBeginnerCourses(ABC):
def RegisterIntroductionToComputerScience(self):
pass
class RegisterAdvanceCourses(ABC):
def RegisterAdvancedAlgorithms(self):
pass
This way we can have two distinct and smaller classes. FirstYearStudent
can directly be a subclass of RegisterBeginnerCourses
. SecondYearStudent
can be a subclass of both RegisterBeginnerCourses
and RegisterAdvanceCourses
superclasses. This is possible in Python and is called multiple inheritance. This way, we do not have to handle exceptions for inherited methods that are not used. It looks way more clean and neat.
class FirstYearStudent(RegisterBeginnerCourses):
def RegisterIntroductionToComputerScience(self):
print("Register to Introduction to Computer Science Course")
class SecondYearStudent(RegisterBeginnerCourses, RegisterAdvanceCourses):
def RegisterIntroductionToComputerScience(self):
print("Register to Introduction to Computer Science Course")
def RegisterAdvancedAlgorithms(self):
print("Register to Advanced Algorithms Course")
RECAP
This was the fourth principle of SOLID. I tried to keep the examples as short as possible to focus on the concepts. I hope you enjoyed reading this. If this kind of stuff is interesting to you, you might also enjoy my write-up about the first two principles of SOLID. Stay tuned for my last write-up! It is going to be about the last principle of SOLID: Dependency Inversion. Coming soon!