SOLID: Принцип открытости-закрытости (OCP)

Принцип открытости-закрытости (OCP) — один из пяти принципов SOLID, который сформулирован следующим образом: сущности (классы) должны быть открыты для расширения, но закрыты для модификации. Что это означает и как реализуется в C# — рассмотрим в этой части.

Принцип открытости-закрытости (OCP) в C#

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

Здесь нам необходимо понять две вещи. Первая — открытость для расширения, а вторая — закрытость для модификации. Открытость для расширения означает, что нам нужно проектировать сущности программного обеспечения таким образом, чтобы можно было легко добавлять новые обязанности при появлении новых требований. С другой стороны, закрытость для модификации означает, что мы не должны модифицировать сущность, пока не найдём ошибки. Почему это важно, давайте разберемся.

Для расширения мы можем использовать полиморфизм или другие методы для добавления новых функций путём написания нового кода.

Проблемы несоблюдения принципа OCP в C#

Если не следовать принципу OCP в процессе разработки приложения, то в итоге можете столкнуться со следующими проблемами:

  1. Если вы разрешаете классу или функции добавлять новую логику, то вам, как разработчику, необходимо протестировать все функции приложения, включая старые и новые.
  2. Как разработчик, вы также обязаны заранее информировать команду QA (контроля качества) об изменениях, чтобы они могли подготовиться к регрессионному тестированию и тестированию новых функций.
  3. Если вы не следуете принципу «открыто-закрыто», это также нарушает принцип единственной ответственности, поскольку класс будет выполнять несколько задач.
  4. Реализация всех функциональных возможностей в одном классе очень затрудняет поддержку.

Исходя из вышеперечисленных ключевых моментов, при разработке приложения на C# мы должны следовать принципу открытости-закрытости.

Как применить принцип открытости-закрытости (OCP) в C#?

  1. Самый простой способ реализовать принцип открытости-закрытости в C# — добавлять новые функции, создавая новые производные классы, которые должны наследоваться от исходного базового класса.
  2. Другой способ — предоставить клиенту доступ к расширению исходного класса с помощью абстракций, например. интерфейса.

Таким образом, когда в любой момент времени возникает необходимость в изменении или добавлении новых требований, вместо того, чтобы вносить изменения в существующую функциональность, всегда лучше и целесообразнее создавать новые производные классы и оставлять реализацию исходного класса без изменений. Давайте разберём это на примере. Как и в предыдущей части про SRP, сначала мы рассмотрим пример, в котором не соблюдается принцип открытости-закрытости и поймём, какие проблемы возникают, если не следовать OCP. А, затем, мы рассмотрим тот же пример, следуя принципу OCP в C#.

Пример использования принципа открытости-закрытости в C#

Пример без использования OCP в C#

Вернемся к нашему классу Invoice. Мы добавили в этот класс ещё один метод, связанный с его функциональностью:

 public enum InvoiceType
 {
     FinalInvoice,
     ProposedInvoice
 };

 public class Invoice
 {
     //поля класса

     public double GetInvoiceDiscount(double amount, InvoiceType invoiceType)
     {
         double finalAmount = 0;
         if (invoiceType == InvoiceType.FinalInvoice)
         {
             finalAmount = amount - 100;
         }
         else if (invoiceType == InvoiceType.ProposedInvoice)
         {
             finalAmount = amount - 50;
         }
         return finalAmount;
     }

     //прочие методы класса
}

Как вы можете видеть, мы создали метод GetInvoiceDiscount(). В рамках этого метода GetInvoiceDiscount() мы вычисляем окончательную сумму в зависимости от типа счёта-фактуры. В настоящее время у нас есть два типа счетов-фактур: окончательный и предварительный. Логику мы реализовали с помощью условия if-else.

Завтра, если появится ещё один тип счёта-фактуры, нам нужно будет изменить логику метода GetInvoiceDiscount(), добавив в исходный код метода ещё один блок ifИзменение исходного кода для выполнения нового требования нарушает принцип открытости-закрытости в C#.

Предположим, что мы снова и снова меняем метод GetInvoiceDiscount() класса Invoice. В этом случае нам нужно убедиться, что предыдущие и новые функции работают должным образом, снова протестировав существующие функции.

Если мы хотим следовать принципу открытости-закрытости в приведенном выше примере, нам нужно добавить новый класс, когда потребуется добавить новый тип счета-фактуры. В результате текущие функции, которые уже реализованы, останутся без изменений. Преимущество в том, что нам нужно только протестировать и проверить новые классы.

Пример с использованием OCP в C#

В следующем примере показано использование принципа открытости-закрытости (OCP) в C#. Как вы можете видеть в приведенном ниже коде, мы создали три класса: FinalInvoiceProposedInvoice и RecurringInvoice. Все эти три класса наследуются от базового класса Invoice, и при желании они могут переопределить метод GetInvoiceDiscount(), который объявлен как виртуальный в базовом классе Invoice.

public class Invoice
{
    public virtual double GetInvoiceDiscount(double amount)
    {
        return amount - 10;
    }
}

public class FinalInvoice : Invoice
{
    public override double GetInvoiceDiscount(double amount)
    {
        return base.GetInvoiceDiscount(amount) - 50;
    }
}
public class ProposedInvoice : Invoice
{
    public override double GetInvoiceDiscount(double amount)
    {
        return base.GetInvoiceDiscount(amount) - 40;
    }
}
public class RecurringInvoice : Invoice
{
    public override double GetInvoiceDiscount(double amount)
    {
        return base.GetInvoiceDiscount(amount) - 30;
    }
}

Теперь, если потребуется добавить ещё один тип счёта, нам нужно будет создать новый класс, унаследовав его от класса Invoice, и при необходимости переопределить метод GetInvoiceDiscount(). Важно помнить, что мы не меняем код класса Invoice. Теперь класс Invoice закрыт для изменений, но он открыт для расширения, так как позволяет создавать новые классы, производные от базового класса Invoice, который следует принципу открытости-закрытости в C#. 

Преимущества принципа открытости закрытости в C#

Ниже перечислены преимущества применения принципа открытости-закрытости в C#:

  • Минимальный риск возникновения ошибок: при соблюдении OCP существующий код остаётся неизменным при добавлении новых функций. Это значительно снижает риск возникновения ошибок в уже протестированном и проверенном коде, обеспечивая стабильность и надёжность.
  • Повышенная возможность повторного использования: компоненты, предназначенные для расширения (но не для изменения), можно повторно использовать в разных частях приложения или даже в разных проектах.
  • Улучшенная поддержка: поскольку основная функциональность существующих классов не меняется, поддержка приложения становится проще. Изменения вносятся путем добавления нового кода, а не изменения старого, что помогает поддерживать чистоту и организованность кодовой базы.
  • Эффективное использование концепции ООП: применение OCP часто приводит к использованию интерфейсов и абстрактных классов, способствуя использованию наследования и полиморфизма (концепции ООП). Это приводит к созданию более гибкой и динамичной архитектуры, в которой поведение может динамически изменяться во время выполнения.
Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии