Приведите пример нарушения принципа открытости/закрытости (Open/Closed Principle) из SOLID.

Ответ

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

Пример нарушения: Класс ReportGenerator, который изменяется каждый раз при добавлении нового формата отчета.

// НАРУШЕНИЕ OCP: Класс приходится МОДИФИЦИРОВАТЬ для добавления поддержки нового типа.
class ReportGenerator {
    public String generateReport(String type, Data data) {
        if (type.equals("PDF")) {
            // Логика генерации PDF
            return generatePdf(data);
        } else if (type.equals("HTML")) {
            // Логика генерации HTML
            return generateHtml(data);
        } else if (type.equals("CSV")) {
            // Логика генерации CSV (добавлена позже)
            return generateCsv(data);
        }
        throw new IllegalArgumentException("Unsupported report type: " + type);
    }

    private String generatePdf(Data d) { /* ... */ }
    private String generateHtml(Data d) { /* ... */ }
    private String generateCsv(Data d) { /* ... */ } // Пришлось добавить новый метод
}

Проблемы такого подхода:

  1. Риск регрессии: Изменение кода для добавления CSV может случайно сломать генерацию PDF или HTML.
  2. Нарушение SRP: Класс берет на себя ответственность за знание о всех возможных форматах.
  3. Сложность тестирования: Монолитный метод, который нужно тестировать на все большее число сценариев.

Решение, соответствующее OCP:

// 1. Абстракция, закрытая для модификации.
interface ReportGenerator {
    String generate(Data data);
}

// 2. Конкретные реализации, открытые для расширения.
class PdfReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* ... */ }
}

class HtmlReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* ... */ }
}

// 3. Новый формат добавляется БЕЗ изменения существующего кода.
class CsvReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* ... */ }
}

// 4. Клиентский код использует абстракцию.
class ReportService {
    private final ReportGenerator generator;
    public ReportService(ReportGenerator generator) { this.generator = generator; }
    public void createReport(Data data) {
        String report = generator.generate(data); // Закрыто для модификации
        // ...
    }
}

Теперь для добавления формата XML нужно лишь создать новый класс XmlReportGenerator, не трогая ReportService, PdfReportGenerator или другие существующие генераторы.

Ответ 18+ 🔞

А, слушай, смотри, вот есть же такой принцип, блядь, открытости-закрытости, OCP. Звучит как хуйня какая-то из учебника, но сейчас я тебе так объясню, что ты, сука, сам от себя офигеешь.

Представь, ты написал класс, который отчёты генерирует. И всё у него пиздец как хорошо. PDF, HTML — всё пашет. А потом приходит менеджер, ёпта, и такой: «А давайте ещё CSV, это ж просто!». И ты такой: «Да не вопрос, ща добавлю».

И вот ты лезешь в свой красивый класс ReportGenerator и начинаешь там ковыряться. Добавляешь ещё один if, ещё один приватный метод... И вроде CSV работает, но вдруг, сука, HTML-отчёт начинает выводить какую-то дичь! Ты нихуя не трогал логику HTML, но что-то пошло не так. Это и есть пиздец, Карл! Ты модифицировал существующий рабочий код и всё сломал.

// Вот это, блядь, классика жанра — как делать НЕ НАДО.
class ReportGenerator {
    public String generateReport(String type, Data data) {
        if (type.equals("PDF")) {
            return generatePdf(data);
        } else if (type.equals("HTML")) {
            return generateHtml(data);
        } else if (type.equals("CSV")) { // О, бля, новый формат! Опять лезть в этот метод?
            return generateCsv(data);
        }
        throw new IllegalArgumentException("Unsupported report type: " + type);
    }
    // ... куча приватных методов
}

Вот этот класс — он как та обезьяна с гранатой. Каждый раз, когда нужно добавить новый формат, ты суёшь руки внутрь и дёргаешь за провода. Рано или поздно всё ебнёт.

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

Как это сделать? Да элементарно, ёпта! Вместо этой кучи if-ов делаем абстракцию.

// 1. Делаем интерфейс. Это наша нерушимая конституция.
interface ReportGenerator {
    String generate(Data data); // Подписался — делай, мудак.
}

// 2. Реализации. Каждая знает свой формат и нихуя больше.
class PdfReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* Генерирую PDF и в ус не дую */ }
}

class HtmlReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* Кодю HTML, как бог */ }
}

// 3. Хочешь CSV? Ну так на, расширяйся, блядь!
// Ни одно существующее тело класса выше я даже не открывал.
class CsvReportGenerator implements ReportGenerator {
    @Override
    public String generate(Data data) { /* Запятые, сука, везде */ }
}

// 4. А главный сервис теперь тупо работает с абстракцией.
class ReportService {
    private final ReportGenerator generator; // Вот тебе генератор, какой дали — такой и юзаю.
    public ReportService(ReportGenerator generator) { this.generator = generator; }

    public void createReport(Data data) {
        String report = generator.generate(data); // И мне похуй, PDF там, HTML или XML от ядрёной воши.
        // ...
    }
}

Суть, блядь, в чём? Если завтра скажут: «Нам нужен отчёт в формате голубиной почты», ты просто создаёшь новый класс CarrierPigeonReportGenerator. И ни одна строчка в PdfReportGenerator, HtmlReportGenerator или ReportService не дрогнет! Они закрыты, блядь, как швейцарский банк. А система — открыта для бесконечного расширения.

Вот и весь принцип. Не лезь с сапогами в работающий код, а просто подставь новую детальку сбоку. Красота, ёпта!