Как писать код, который не сложно поддерживать
Один класс — одна причина для изменения
class User:
def save(self): ...
def send_email(self): ...
def log_action(self): ...
Три причины изменить: БД, почта, логи
class User:
def __init__(self, repo, mailer, logger):
self.repo = repo
self.mailer = mailer
self.logger = logger
def save(self):
self.repo.save(self)
self.logger.log("User saved")
def notify(self):
self.mailer.send(self.email)
Класс не делает всё сам — делегирует
Расширять — можно. Менять — нет.
def render_chart(data, type):
if type == 'bar':
draw_bar(data)
elif type == 'pie':
draw_pie(data)
# Как добавить 'line'? Придётся менять!
from abc import ABC, abstractmethod
class Chart(ABC):
@abstractmethod
def render(self, data): pass
class BarChart(Chart): ...
class PieChart(Chart): ...
class LineChart(Chart): ... # легко добавить!
def render(chart: Chart, data):
chart.render(data)
Новый тип? Новая реализация — без правки старого кода
Подкласс должен заменять родительский класс
class Bird:
def fly(self): ...
class Penguin(Bird):
def fly(self):
raise Exception("Я не летаю!")
fly() ломает ожидания
class FlyingBird(ABC):
@abstractmethod
def fly(self): pass
class Sparrow(FlyingBird): ...
class Bird:
pass
class Penguin(Bird): # просто птица, не летающая
pass
Наследование по поведению, а не по имени
Не заставляйте клиентов зависеть от того, что им не нужно
class Worker(ABC):
@abstractmethod
def work(self): pass
@abstractmethod
def eat(self): pass # А робот должен есть?
class Robot(Worker):
def work(self): ...
def eat(self): raise NotImplementedError
class Workable(ABC):
@abstractmethod
def work(self): pass
class Eatable(ABC):
@abstractmethod
def eat(self): pass
class Human(Workable, Eatable): ...
class Robot(Workable): # только работает
Каждый интерфейс — свой
Зависимость от абстракций, а не от деталей
class Notifier:
def __init__(self):
self.email = EmailService() # жёсткая зависимость
def notify(self, msg):
self.email.send(msg)
Что если нужно SMS?
class MessageService(ABC):
@abstractmethod
def send(self, msg): pass
class Notifier:
def __init__(self, service: MessageService):
self.service = service
def notify(self, msg):
self.service.send(msg)
# Можно передать EmailService, SMSService, SlackService...
Гибко, тестируемо, масштабируемо
Чтобы ваш код:
“Принципы SOLID — это не правила. Это щит от технического долга.”