1.5. S.O.L.I.D. Principles
SRP - Single Responsibility Principle
OCP - Open/Closed Principle
LSP - Liskov Substitution Principle
ISP - Interface Segregation Principle
1.5.1. Single Responsibility Principle
There should never be more than one reason for a class to change. In other words, every class should have only one responsibility.
—SOLID, Single Responsibility Principle, Robert C. Martin
SetUp:
>>> from dataclasses import dataclass
>>> from random import randint
>>> from typing import ClassVar
Bad:
>>> @dataclass
... class Hero:
... HEALTH_MIN: ClassVar[int] = 10
... HEALTH_MAX: ClassVar[int] = 20
... health: int = 0
... position_x: int = 0
... position_y: int = 0
...
... def position_set(self, x: int, y: int) -> None:
... self.position_x = x
... self.position_y = y
...
... def position_change(self, right=0, left=0, down=0, up=0):
... x = self.position_x + right - left
... y = self.position_y + down - up
... self.position_set(x, y)
...
... def position_get(self) -> tuple:
... return self.position_x, self.position_y
...
... def __post_init__(self) -> None:
... self.health = randint(self.HEALTH_MIN, self.HEALTH_MAX)
...
... def is_alive(self) -> bool:
... return self.health > 0
...
... def is_dead(self) -> bool:
... return self.health <= 0
Good:
>>> @dataclass
... class HasHealth:
... HEALTH_MIN: ClassVar[int]
... HEALTH_MAX: ClassVar[int]
... health: int = 0
...
... def __post_init__(self) -> None:
... self.health = randint(self.HEALTH_MIN, self.HEALTH_MAX)
...
... def is_alive(self) -> bool:
... return self.health > 0
...
... def is_dead(self) -> bool:
... return self.health <= 0
>>>
>>>
>>> @dataclass
... class HasPosition:
... position_x: int = 0
... position_y: int = 0
...
... def position_set(self, x: int, y: int) -> None:
... self.position_x = x
... self.position_y = y
...
... def position_change(self, right=0, left=0, down=0, up=0):
... x = self.position_x + right - left
... y = self.position_y + down - up
... self.position_set(x, y)
...
... def position_get(self) -> tuple:
... return self.position_x, self.position_y
>>>
>>>
>>> class Hero(HasHealth, HasPosition):
... HEALTH_MIN: int = 10
... HEALTH_MAX: int = 20
1.5.2. Open/Closed Principle
Software entities ... should be open for extension, but closed for modification.
—SOLID, Open/Closed Principle, Robert C. Martin
SetUp:
>>> from abc import abstractproperty, abstractmethod, ABC
Good:
>>> class Document(ABC):
... @abstractproperty
... def EXTENSIONS(self) -> list[str]: ...
...
... @abstractmethod
... def display(self):
... raise NotImplementedError('Display method must be implemented')
...
... def __init__(self, filename):
... self.filename = filename
...
... def __new__(cls, filename):
... basename, extension = filename.split('.')
... plugins = cls.__subclasses__()
... for plugin in plugins:
... if extension in plugin.EXTENSIONS:
... instance = object.__new__(plugin)
... instance.__init__(filename)
... return instance
... raise ValueError('Unknown file type')
>>>
>>>
>>> class PDFDocument(Document):
... EXTENSIONS = ['pdf']
...
... def display(self):
... print('Displaying PDF document')
>>>
>>>
>>> class WordDocument(Document):
... EXTENSIONS = ['doc', 'docx']
...
... def display(self):
... print('Displaying Word document')
1.5.3. Liskov Substitution Principle
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
—SOLID, Liskov Substitution Principle, Robert C. Martin
SetUp:
>>> class User:
... def login(self): ...
... def logout(self): ...
>>>
>>> class Admin(User):
... def add_users(self): ...
... def del_users(self): ...
>>> def auth(account):
... account.login()
... account.logout()
Good:
>>> mark = User()
>>> auth(mark) # will work
>>> melissa = Admin()
>>> auth(melissa) # has to work
1.5.4. Interface Segregation Principle
Clients should not be forced to depend upon interfaces that they do not use.
—SOLID, Interface Segregation Principle, Robert C. Martin
SetUp:
>>> from typing import Protocol
Bad:
>>> class Account(Protocol):
... def login(self): ...
... def logout(self): ...
Good:
>>> class CanLogin(Protocol):
... def login(self): ...
>>>
>>> class CanLogout(Protocol):
... def logout(self): ...
1.5.5. Dependency Inversion Principle
Depend upon abstractions, [not] concretes.
—SOLID, Dependency Inversion Principle, Robert C. Martin
SetUp:
>>> class Account:
... pass
>>>
>>> class User(Account):
... pass
>>>
>>> class Admin(Account):
... pass
Bad:
>>> mark: User = User()
>>> melissa: Admin = Admin()
Good:
>>> mark: Account = User()
>>> melissa: Account = Admin()