О чем говорит принцип open/closed?

Ответ

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

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

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

class Logger {
public:
    void log(const std::string& message, const std::string& type) {
        if (type == "FILE") {
            // запись в файл
        } else if (type == "CONSOLE") {
            // вывод в консоль
        }
        // Добавление нового типа (например, "NETWORK") требует МОДИФИКАЦИИ этого класса!
    }
};

Пример соблюдения OCP с использованием полиморфизма:

// Абстрактный базовый класс, закрытый для модификации
class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void log(const std::string& message) = 0;
};

// Конкретные реализации, открытые для расширения
class FileLogger : public ILogger {
public:
    void log(const std::string& message) override { /* запись в файл */ }
};

class ConsoleLogger : public ILogger {
public:
    void log(const std::string& message) override { /* вывод в консоль */ }
};

// НОВЫЙ тип логгера можно добавить БЕЗ изменения существующего кода
class NetworkLogger : public ILogger {
public:
    void log(const std::string& message) override { /* отправка по сети */ }
};

// Клиентский код работает с абстракцией
void process(ILogger& logger) {
    logger.log("Operation completed");
}

Таким образом, для добавления нового способа логирования мы создаем новый класс, а не меняем старые, что снижает риск регрессий и повышает поддерживаемость кода.

Ответ 18+ 🔞

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

Представь себе, что у тебя есть класс, как тот самый старый, но верный чайник. Работает, греет воду, доверия к нему — ебать ноль, но он свой долг исполняет. И вот тебе понадобилось, чтобы он ещё и свистком оповещал, когда закипит. По принципу OCP, ты НЕ должен лезть внутрь этого чайника с паяльником и изолентой, рискуя всё сломать (закрыт для модификации). Вместо этого ты покупаешь свисток, который насаживается на носик (открыт для расширения). Старый код не трогал, новую фичу добавил. Всё гениально и просто.

А теперь смотри на этот пиздец, который нарушает принцип. Типичная история, знакомо до боли.

class ReportGenerator {
public:
    void generateReport(const std::string& format) {
        if (format == "PDF") {
            // Генерация PDF... куча кода
        } else if (format == "HTML") {
            // Генерация HTML... ещё куча кода
        }
        // Завтра босс придёт и скажет: "А сделай-ка нам ещё в Excel, да в JSON!"
        // И тебе придётся ЛЕЗТЬ В ЭТУ ЖИРНУЮ ФУНКЦИЮ И ДОПИСЫВАТЬ if-ы.
        // Это и есть пиздопроебибна архитектура. Один день — и ты уже ненавидишь свой код.
    }
};

Видишь? Каждый раз, когда нужен новый формат, ты впендюриваешь новый if в тело метода. Терпения ноль, ебать! Рано или поздно эта функция превратится в манду с ушами на 500 строк, где что-то сломать — раз плюнуть.

А теперь как надо, по-человечески, с полиморфизмом. Э, бошка, думай!

// Абстрактная основа. Её мы НЕ ТРОГАЕМ. Она закрыта, как шлюха в понедельник.
class ReportExporter {
public:
    virtual ~ReportExporter() = default;
    virtual void exportReport(const Data& data) = 0;
};

// Конкретные реализации. Их можно плодить, как кроликов.
class PdfExporter : public ReportExporter {
public:
    void exportReport(const Data& data) override { /* Магия создания PDF */ }
};

class HtmlExporter : public ReportExporter {
public:
    void exportReport(const Data& data) override { /* Разметка, теги, всё дела */ }
};

// И вот, блядь, приходит требование на Excel. Что делаем?
// Правильно, НЕ ЛЕЗЕМ в старые классы, а создаём новый.
class ExcelExporter : public ReportExporter { // ВОТ ОНО, РАСШИРЕНИЕ!
public:
    void exportReport(const Data& data) override { /* Формулы, листы, хуиты */ }
};

// Клиентский код спит спокойно. Он работает с абстракцией.
void createAndSendReport(ReportExporter& exporter, const Data& data) {
    // ... какая-то подготовка данных ...
    exporter.exportReport(data); // И ему похуй, PDF там, HTML или новая фича.
}

Ни хуя себе, правда? Суть в том, что система теперь не ломается от каждого чиха начальства. Захотел новый формат — отнаследовался от базового класса, реализовал один метод, и вписал свою фабрику или конфиг. Старый, отлаженный код не накрылся медным тазом, все тесты на него по-прежнему работают. Это и есть красота.

Если коротко: Не ковыряй работающее. Оберни его в абстракцию и навешивай новые фичи сбоку. И будет тебе счастье, а не хиросима в кодовой базе каждый спринт.