Что такое принцип открытости/закрытости (OCP) в ООП?

Ответ

Принцип открытости/закрытости (Open-Closed Principle, OCP) — один из пяти принципов SOLID, который гласит:

Программные сущности (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации.

Это означает, что вы должны иметь возможность добавлять новую функциональность (открыт для расширения) без изменения существующего, уже протестированного кода (закрыт для модификации). Соблюдение OCP повышает стабильность, поддерживаемость и гибкость системы, снижая риск появления ошибок при добавлении новых возможностей.

Пример нарушения OCP:

Представим калькулятор площади, который напрямую зависит от конкретных типов фигур. При добавлении новой фигуры (Circle), придется изменять AreaCalculator.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

class Circle:
    def __init__(self, radius):
        self.radius = radius

class AreaCalculator:
    def calculate(self, shapes):
        total = 0
        for shape in shapes:
            if isinstance(shape, Rectangle):
                total += shape.width * shape.height
            elif isinstance(shape, Circle): # При добавлении Circle, пришлось модифицировать AreaCalculator
                total += 3.14 * shape.radius ** 2
        return total

Правильный подход с соблюдением OCP:

Используя абстракции (интерфейсы или абстрактные классы) и полиморфизм, мы можем расширять систему, не изменяя существующий код.

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        """Возвращает площадь фигуры."""
        pass

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return math.pi * self.radius ** 2

class AreaCalculator:
    def calculate(self, shapes: list[Shape]) -> float:
        """Вычисляет общую площадь списка фигур."""
        return sum(shape.area() for shape in shapes)

# Теперь можно легко добавить новую фигуру (например, Triangle), не изменяя AreaCalculator
class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

# Использование:
calculator = AreaCalculator()
shapes_list = [
    Rectangle(10, 5),
    Circle(7),
    Triangle(4, 6)
]
print(f"Общая площадь: {calculator.calculate(shapes_list)}")

В этом примере AreaCalculator закрыт для модификации, потому что он работает с абстракцией Shape. Чтобы добавить новую фигуру, достаточно создать новый класс, реализующий Shape, без изменения AreaCalculator. Это делает систему более гибкой и устойчивой к изменениям.