Open/Closed Principle (OCP) — принцип открытости/закрытости

1 минута чтения #php#solid

Принцип открытости/закрытости (Open/Closed Principle, OCP) — один из самых практичных и популярных принципов SOLID. Он говорит, что классы должны быть открыты для расширения, но закрыты для изменения.

Проще говоря:

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

Почему это важно?
Потому что любое изменение существующих классов может привести к багам в тех местах, где этот код уже используется. Если система написана по OCP, вы можете добавлять новые возможности без риска “сломать” старую логику.

Зачем нужен OCP?

Этот принцип помогает достигать сразу двух целей:

1. Предсказуемость кода

Если логика уже работает — трогать её опасно. OCP позволяет добавить новое поведение без вмешательства в существующую реализацию.

2. Гибкость системы

Добавлять новые типы объектов, новые обработчики, новые стратегии становится проще — вы просто создаёте новые классы, которые расширяют базовый функционал.

3. Минимизация багов

Вы не ломаете то, что уже написали и протестировали. Значит, реже что-то отваливается в других частях проекта.

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

Допустим, у нас есть класс, который считает стоимость доставки:

class DeliveryCalculator
{
    public function calculate(string $type, float $distance): float
    {
        if ($type === 'standard') {
            return $distance * 5;
        }

        if ($type === 'express') {
            return $distance * 10;
        }

        if ($type === 'overnight') {
            return $distance * 20;
        }

        return 0;
    }
}

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

Как исправить по OCP

Мы создаём интерфейс:

interface DeliveryType
{
    public function calculate(float $distance): float;
}

Реализуем разные типы доставки:

class StandardDelivery implements DeliveryType
{
    public function calculate(float $distance): float
    {
        return $distance * 5;
    }
}

class ExpressDelivery implements DeliveryType
{
    public function calculate(float $distance): float
    {
        return $distance * 10;
    }
}

class OvernightDelivery implements DeliveryType
{
    public function calculate(float $distance): float
    {
        return $distance * 20;
    }
}

А сама логика расчёта теперь выглядит так:

class DeliveryCalculator
{
    public function calculate(DeliveryType $delivery, float $distance): float
    {
        return $delivery->calculate($distance);
    }
}

Теперь, чтобы добавить новый тип доставки, достаточно создать новый класс:

class InternationalDelivery implements DeliveryType
{
    public function calculate(float $distance): float
    {
        return $distance * 50;
    }
}

Калькулятор менять не нужно — он закрыт для модификации, но открыт для расширения.

Когда нужно применять OCP?

Принцип отлично подходит, когда:

  • количество возможных вариантов будет расти
  • система требует гибкости
  • нужны разные стратегии поведения
  • в проекте часто меняются требования

Например:

  • способы оплаты
  • типы уведомлений
  • виды доставки
  • варианты логирования
  • обработчики событий

Краткое резюме

  • OCP говорит: добавляй новое — но не трогай старое
  • Используйте интерфейсы, абстракции и композицию
  • Выносите меняющиеся части в отдельные классы
  • Реализуйте разные стратегии вместо “вечного IF”