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

«Что такое принцип открытости/закрытости (OCP)?» — вопрос из категории ООП, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

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

Нарушение OCP (Антипаттерн): Код, где для добавления нового типа приходится лезть в существующую логику (через if/switch).

public class ReportExporter // Нарушает OCP
{
    public void Export(ReportData data, string format)
    {
        if (format == "PDF")
        {
            // Логика генерации PDF (100 строк кода)
        }
        else if (format == "CSV")
        {
            // Логика генерации CSV
        }
        // Добавление нового формата (например, Excel) требует изменения этого метода!
    }
}

Соблюдение OCP: Используем полиморфизм и абстракции. Новый формат = новый класс.

// 1. Абстракция (интерфейс), закрытая для модификации.
public interface IReportExporter
{
    void Export(ReportData data);
    string FormatName { get; }
}

// 2. Конкретные реализации, открытые для расширения.
public class PdfReportExporter : IReportExporter
{
    public string FormatName => "PDF";
    public void Export(ReportData data)
    {
        // Специфичная логика для PDF
        Console.WriteLine($"Generating PDF from {data.Title}");
    }
}

public class CsvReportExporter : IReportExporter
{
    public string FormatName => "CSV";
    public void Export(ReportData data)
    {
        // Специфичная логика для CSV
        Console.WriteLine($"Generating CSV with {data.Rows.Count} rows");
    }
}

// 3. Основной класс, который не нужно менять при добавлении новых форматов.
public class ReportProcessor
{
    private readonly IEnumerable<IReportExporter> _exporters;
    public ReportProcessor(IEnumerable<IReportExporter> exporters) => _exporters = exporters;

    public void ExportReport(ReportData data, string desiredFormat)
    {
        var exporter = _exporters.FirstOrDefault(e => e.FormatName == desiredFormat);
        if (exporter == null)
            throw new NotSupportedException($"Format '{desiredFormat}' is not supported.");

        exporter.Export(data); // Полиморфный вызов
    }
}

// 4. Добавление поддержки Excel НЕ требует изменения существующих классов.
public class ExcelReportExporter : IReportExporter
{
    public string FormatName => "Excel";
    public void Export(ReportData data) { /* Новая логика */ }
}

Преимущества следования OCP:

  1. Снижение риска регрессии: Не трогая рабочий код, вы не можете его случайно сломать.
  2. Упрощение тестирования: Каждый экспортер тестируется изолированно.
  3. Гибкость и масштабируемость: Новая функциональность добавляется через расширение, а не через модификацию.