In all the below examples they are not complete but basically show you in principle how to break a specified principle and what you should have done.
Single Responsibility Principle
The Single Responsibility Principle (SRP) basically means that a class should have one and only one responsibility, it should be encapsulated within the class, the class should not be a GOD object.
Breaking the SRP principle | #Below is Given a class which has two responsibilities class User: def __init__(self, name: str): self.name = name def get_name(self) -> str: pass def save(self, user: User): pass |
Following the SRP rule | # Break the two responsibilities into two classes class User: def __init__(self, name: str): self.name = name def get_name(self): pass class UserDB: def get_user(self, id) -> User: pass def save(self, user: User): pass |
The Open-Closed Principle (OCP) should be open for extension (extending) but closed for modification, the reason for this is that if a class has been tested and you know it works don't change it. This class could be at any clients location or in a library and thus making changes could cause issues. It uses interfaces instead of superclasses to allow different implementations which you can easily substitute without changing the code that uses them.
Breaking the OCP rule | class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def give_discount(self): ## This was added at a later date if self.customer == 'fav': return self.price * 0.2 if self.customer == 'vip': return self.price * 0.4 |
Following the OCP rule | class Discount: def __init__(self, customer, price): self.customer = customer self.price = price def get_discount(self): return self.price * 0.2 class VIPDiscount(Discount): ## we extend the original Discount class and add the additional features def get_discount(self): return super().get_discount() * 2 |
The Liskov Subsitution Principle (LSP) is that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass. So any functions that use references to base classes must be able to use objects of the derived class without knowing it, for example you inherit a class method that starts to break things or behaves oddly.
Breaking the LSP rule | class Rectangle: def __init__(self, width=0, height=0): self.width = width self.height = height def setwidth(self, width): self.width = width def setheight(self, height): self.height = height def calculate(self): print("Rectangle width: " + str(self.width) + " height: " + str(self.height)) return self.width * self.height class Square(Rectangle): def __init__(self, size): super().__init__(size, size) def setwidth(self, width): super().setwidth(width) def setheight(self, height): super().setheight(height) rec = Rectangle(2, 3) rec.setheight(10) print(str(rec.calculate())) sq = Square(5) # this works print(str(sq.calculate())) sq.setheight(10) # we only need to set either height/width, so should be 10 (width) * 10 (height) print(str(sq.calculate())) |
Following the LSP rule | class Rectangle: def __init__(self, width=0, height=0): self.width = width self.height = height def setwidth(self, width): self.width = width def setheight(self, height): self.height = height def calculate(self): print("Rectangle width: " + str(self.width) + " height: " + str(self.height)) #print(type(self).__name__) # we could get the class name and if square set both height and width return self.width * self.height class Square(Rectangle): def __init__(self, size): super().__init__(size, size) def setwidth(self, width): super().setwidth(width) super().setheight(width) # we could set both height and width here def setheight(self, height): super().setheight(height) super().setwidth(height) # we could set both height and width here rec = Rectangle(2, 3) rec.setheight(10) print(str(rec.calculate())) sq = Square(5) # this works print(str(sq.calculate())) sq.setheight(10) # this now works print(str(sq.calculate())) |
Interface Segregation Principle
The Interface Segregation Principle (ISP) states that no client should be forced to depend on methods it does not use, you should split interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. There is a design pattern that you can also use here called the decorator pattern which I will cover in another section.
Breaking the ISP rule | ## This class methods that may or may not be used if extended class Machine: def print(self): pass def fax(self): pass def scan(self): pass class OldPrinter(Machine): def print(self): pass def fax(self): pass ## We don't need this def scan(self): pass ## We don't need this |
Following the ISP rule | ## Separate the methods into classes of there own then inherit only what you need class Printer: def print(self): class Fax: def fax(self): class Scanner: def scan(self): class OldPrinter(Printer): .... class PrinterScanner(Printer, Scanner): .... |
Dependency Inversion Principle
The Dependency Inversion Principle (DIP) requires that High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other, by decoupling components it allows for easier testing, most often this is solved by using dependency injection which is different than dependency inversion.
DIP example | ## Low-level component class AuthenticationForUser(): def __init__(self, connector:Connector): self.connection = connector.connect() def authenticate(self, credentials): pass def is_authenticated(self): pass def last_login(self): pass ## High-level components class AnonymousAuth(AuthenticationForUser): pass class GithubAuth(AuthenticationForUser): def last_login(self): pass class FacebookAuth(AuthenticationForUser): pass class Permissions() def __init__(self, auth: AuthenticationForUser) self.auth = auth def has_permissions(): pass class IsLoggedInPermissions (Permissions): def last_login(): return auth.last_log |
Below is a table that summaries the SOLID principles
Single Responsibility Principle |
|
Open-Closed Principle |
|
Liskov Substitution Principle |
|
Iterface Segregation Principle |
|
Dependency Inversion Principle |
|