Skip to content

Latest commit

 

History

History
93 lines (58 loc) · 9.7 KB

Open-Closed_principle.md

File metadata and controls

93 lines (58 loc) · 9.7 KB

Open-Closed Principle

Принцип открыт/закрыт (Open-Closed Principle, OCP)

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

  • Они открыты для расширения.

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

  • Они закрыты для модификации.

    Расширение поведения модуля не сопряжено с изменениями в исходном или двоичном коде модуля. Двоичное исполняемое представление модуля, будь то компонуемая библиотека, DLL или EXE-файл, остается неизменным.


При этом понятия открытости и закрытости определяются так:

  • Модуль называют открытым, если он еще доступен для расширения. Например, имеется возможность расширить множество операций в нем или добавить поля к его структурам данных.

  • Модуль называют закрытым, если он доступен для использования другими модулями. Это означает, что модуль (его интерфейс - с точки зрения скрытия информации) уже имеет строго определенное окончательное описание. На уровне реализации закрытое состояние модуля означает, что модуль можно компилировать, сохранять в библиотеке и делать его доступным для использования другими модулями (его клиентами).

Но всегда ли нам нужно создавать наследника? Нет, не всегда! Даже такой ОО-гуру как Мейер, который описывает в своей книге 12 видов наследования, относится к расширяемости модулей путем создания наследников с разумным прагматизмом:

«Если имеется возможность переписать исходную программу так, чтобы она, без излишнего усложнения, смогла удовлетворять потребности нескольких разновидностей клиентов, то следует это сделать».

Естественно, модуль должен модифицироваться при наличии в нем ошибок:

«Как принцип Открыт-Закрыт, так и переопределение в механизме наследования не позволяет справиться с дефектами разработки, не говоря уже об ошибках в программе. Если в модуле что-то не в порядке, то следует это сразу исправить в исходной программе, не пытаясь разбираться возникающей проблемой в производном модуле»

Принцип единственного выбора

abstract class Importer
{
    public abstract void ImportData();
}
 
static class ImporterFactory
{
    public static Importer Create(string fileName)
    {
        Contract.Requires(!string.IsNullOrEmpty(fileName));
        Contract.Ensures(Contract.Result<Importer>() != null);
 
        var extension = Path.GetExtension(fileName);
        switch (extension)
        {
            case "json":
                return new JsonImporter();
            case "xls":
            case "xlsx":
                return new XlsImporter();
            default:
                throw new InvalidOperationException(
                  "Extension is not supported");
        }
    }

Отвечает ли реализация такой фабрики принципу Open/Close? Или для соответствия этому принципу нам нужно вводить интерфейс IImporterFactory и еще одну иерархию - иерархию фабрик? А кто будет создавать фабрику? С помощью фабрики фабрик?

Фабричный метод сам по себе и так уже прячет от своих клиентов способ получения нужного Importer-а и дополнительный уровень косвенности здесь не нужен.

Вот что пишет Бертран Мейер по этому поводу:

«Необходимо допускать возможность того, что список вариантов, заданных и известных на некотором этапе разработки программы, может в последующем быть изменен путем добавления или удаления вариантов. Чтобы обеспечить реализацию такого подхода к процессу разработки программного обеспечения, нужно найти способ защитить структуру программы от воздействия подобных изменений. Отсюда следует принцип Единственного Выбора»

Принцип единственного выбора: всякий раз, когда система программного обеспечения должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы.

Какую проблему призван решить принцип Открыт/Закрыт?

Смысл принципа OCP довольно прост: дизайн системы должен быть простым и устойчивым к изменениям.

  • Во-первых, за счет абстракции и инкапсуляции. Мы выделяем существенные части системы в виде интерфейсов и абстрактных классов, не задумываясь о реализации, которая будет скрыта от клиентов. Но даже конкретный класс, унаследованный от System.Object представляет собой абстракцию. Класс String абстрагирует нас от конкретного представления строки и многих других подробностей, хотя и не реализует интерфейс IString.

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

  • Во-вторых, за счет наследования. Мы используем наследование для полиморфного поведения, что позволяет использовать базовые классы в подавляющем числе мест приложения, а также использовать повторно значительные куски кода.

!!! Абстракция не подразумевает наличие наследования. Абстракция не может существовать без инкапсуляции! *(книга Гради Буча «Объектно-ориентированный анализ и проектирование»)

Заключение

Что такое OCP? Это фиксация интерфейса класса/модуля, и возможность изменения или подмены реализации/поведения.

Цели OCP: Борьба со сложностью и ограничение изменений минимальным числом модулей.

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