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

«Приведите пример нарушения принципа открытости/закрытости (Open/Closed Principle) из SOLID.» — вопрос из категории ООП, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Принцип открытости/закрытости (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 или другие существующие генераторы.