Ответ
Принцип открытости/закрытости (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:
- Снижение риска регрессии: Не трогая рабочий код, вы не можете его случайно сломать.
- Упрощение тестирования: Каждый экспортер тестируется изолированно.
- Гибкость и масштабируемость: Новая функциональность добавляется через расширение, а не через модификацию.