1. Принцип единственной ответственности (Single Responsibility Principle, SRP)
SOLID принципы в Python: руководство для начинающих
SOLID — это набор принципов объектно-ориентированного программирования, которые помогают создавать гибкий, понятный и поддерживаемый код. Эти правила особенно важны в Python, где динамическая типизация и свобода синтаксиса иногда могут привести к запутанным решениям. Разберем каждый принцип на практических примерах.
1. Принцип единственной ответственности (Single Responsibility Principle, SRP)
Суть: Класс должен решать только одну задачу.
Пример нарушения:
class UserManager:
def __init__(self, user):
self.user = user
def save_user(self):
# Сохранение в базу данных
print(f"User {self.user} saved to database")
def send_email(self, message):
# Отправка email
print(f"Email sent to {self.user}: {message}")
Здесь класс UserManager отвечает и за сохранение данных, и за рассылку писем.
Исправление:
class UserDB:
def save(self, user):
print(f"User {user} saved to database")
class EmailService:
def send(self, user, message):
print(f"Email sent to {user}: {message}")
# Использование:
db = UserDB()
email = EmailService()
db.save("Alice")
email.send("Alice", "Welcome!")
Теперь каждый класс решает одну задачу.
2. Принцип открытости/закрытости (Open-Closed Principle, OCP)
Суть: Классы должны быть открыты для расширения, но закрыты для модификации.
Пример нарушения:
class Discount:
def apply(self, price, user_type):
if user_type == "VIP":
return price * 0.8
elif user_type == "regular":
return price * 0.95
При добавлении новой скидки придется изменять метод apply.
Исправление:
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, price):
pass
class VIPDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.8
class RegularDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.95
class Discount:
def __init__(self, strategy: DiscountStrategy):
self.strategy = strategy
def apply(self, price):
return self.strategy.apply(price)
# Добавляем новую скидку без изменения существующего кода:
class NewYearDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.7
Теперь новые скидки добавляются через создание классов-стратегий.
3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP)
Суть: Подклассы должны заменять родительские классы без изменения поведения программы.
Пример нарушения:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
# Нарушение: изменение поведения сеттеров
def set_width(self, value):
self.width = self.height = value
Квадрат меняет логику установки ширины и высоты, что может привести к ошибкам.
Исправление:
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
Теперь квадрат и прямоугольник не наследуют друг друга, но реализуют общий интерфейс.
4. Принцип разделения интерфейса (Interface Segregation Principle, ISP)
Суть: Клиенты не должны зависеть от методов, которые они не используют.
Пример нарушения:
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
class Robot(Worker):
def work(self):
print("Working...")
def eat(self): # Робот не ест!
raise NotImplementedError
Робот вынужден реализовывать ненужный метод eat.
Исправление:
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
print("Working...")
def eat(self):
print("Eating...")
class Robot(Workable):
def work(self):
print("Working...")
Интерфейсы разделены по функциональности.
5. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP)
Суть: Зависимости должны строиться на абстракциях, а не на конкретных реализациях.
Пример нарушения:
class LightBulb:
def turn_on(self):
print("Light is on")
class Switch:
def __init__(self):
self.bulb = LightBulb() # Зависимость от конкретного класса
def operate(self):
self.bulb.turn_on()
Класс Switch жестко зависит от LightBulb.
Исправление:
class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass
class LightBulb(Switchable):
def turn_on(self):
print("Light is on")
class Fan(Switchable):
def turn_on(self):
print("Fan is on")
class Switch:
def __init__(self, device: Switchable): # Зависимость от абстракции
self.device = device
def operate(self):
self.device.turn_on()
# Использование:
bulb = LightBulb()
fan = Fan()
switch = Switch(fan)
switch.operate() # Выведет: Fan is on
Теперь Switch работает с любыми устройствами, реализующими интерфейс Switchable.
Заключение
SOLID принципы — это не строгие правила, а рекомендации для создания чистого кода. В Python их применение особенно важно из-за гибкости языка. Начните с малого: разделяйте ответственность классов, избегайте «божественных объектов», проектируйте модули так, чтобы их можно было расширять. Со временем следование этим принципам станет привычкой, а ваш код — проще для понимания и тестирования.