1. Принцип единственной ответственности (Single Responsibility Principle, SRP)

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 их применение особенно важно из-за гибкости языка. Начните с малого: разделяйте ответственность классов, избегайте «божественных объектов», проектируйте модули так, чтобы их можно было расширять. Со временем следование этим принципам станет привычкой, а ваш код — проще для понимания и тестирования.