SOLID: Принцип единственной ответственности (SRP)

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

Принцип единственной ответственности в C#?

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

Что означает «Ответственность» в SRP?

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

Как применить принцип единственной ответственности в C #?

Чтобы применить принцип единственной ответственности в C# при разработке классов, необходимо:

  • Определить обязанности: первым шагом является чёткое определение обязанностей вашего класса. Обязанности часто группируются по категориям, таким как доступ к данным, проверка, ведение журнала, кэширование, сериализация, регистрация пользователей, аутентификация и т. д.
  • Создайте отдельные классы: Как только мы определим обязанности, мы должны создать отдельные классы для каждой ответственности. Мы не должны смешивать несколько функций или обязанностей в одном классе. Если класс выполняет более одной ответственности, подумайте о том, чтобы разбить его на несколько классов.
  • Разделение задач: наконец, нам нужно убедиться, что каждый класс выполняет только одну конкретную задачу.

Пример использования принципа единой ответственности (SRP) с использованием C#

Давайте разберём принцип единственной ответственности в C# на примере. Предположим, нам нужно создать класс «Счёт-фактура». Класс «Счёт-фактура» вычисляет различные суммы на основе своих данных. Этот класс «Счёт-фактура» не знает, как получить данные или как отформатировать их для отображения, печати, ведения журнала, отправки по электронной почте и т. д.

Если мы напишем базу данных, бизнес-логику и логику отображения в одном классе, наш класс будет выполнять несколько задач. Тогда изменить одну задачу, не нарушив работу других задач, будет очень сложно. Таким образом, смешивая несколько задач в одном классе, мы получаем следующие недостатки:

  1. Работу класса трудно понять
  2. Класс трудно поддается тестированию
  3. Есть вероятность дублирования логики других частей приложения.

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

Давайте рассмотрим пример, в котором мы не следуем принципу единственной ответственности в C# и посмотрим на проблемы, которые возникают, если мы не будем следовать SRP. Рассмотрим следующую схему, которую мы хотим реализовать в классе Invoice.

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

using System.Net.Mail;

namespace SOLID_PRINCIPLES.SRP
{
    public class Invoice
    {
        public long InvoiceAmount { get; set; }
        public DateTime InvoiceDate { get; set; }

        public void AddInvoice()
        {
            try
            {
                // Здесь код для формирования счёт-фактуры
                // как только счёт-фактура сформирована - отправляем письмо
                MailMessage mailMessage = new MailMessage("EMailFrom", "EMailTo", "EMailSubject", "EMailBody");
                this.SendEmail(mailMessage);
            }
            catch (Exception ex)
            {
                //записываем ошибку в лог
                AddLog(@"c:\ErrorLog.txt", ex.ToString());
            }
        }

        public void AddLog(string file, string text)
        {
            System.IO.File.WriteAllText(file, text);
        }

        public void DeleteInvoice()
        {
            try
            {
                //Здесь код для удаления уже сформированной счёт-фактуры
            }
            catch (Exception ex)
            {
                //записываем ошибку в лог
                AddLog(@"c:\ErrorLog.txt", ex.ToString());
            }
        }

        public void SendEmail(MailMessage mailMessage)
        {
            try
            {
                // код для отправки письма
            }
            catch (Exception ex)
            {
                //записываем ошибку в лог
                AddLog(@"c:\ErrorLog.txt", ex.ToString());
            }
        }
    }
}

При таком подходе, если мы захотим изменить метод ведения журнала или отправки электронных писем, нам нужно будет изменить класс Invoice. Это нарушает принцип единственной ответственности, так как мы изменяем класс Invoice по причине изменения не его функций. Если мы внесём изменения, нам нужно будет протестировать функции ведения журнала, отправки электронных писем и выставления счетов. Теперь давайте обсудим, как реализовать вышеперечисленные функции в соответствии с принципом единственной ответственности в C#.

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

Реализуем тот же пример, следуя SRP. Вот что мы будем делать:

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

Теперь, когда у вас есть описанный выше дизайн, если вы хотите изменить функциональность электронной почты, вам нужно изменить только класс Email, а не классы Invoice и Logging. Аналогично, если вы хотите изменить функциональность Invoice, вам нужно изменить только класс Invoice, а не классы Email и Logging. В C# реализация классов может выглядеть следующим образом:

public class Logger
{
    public void AddLog(string file, string text)
    {
        System.IO.File.WriteAllText(file, text);
    }
}

public class Email
{

    public string EMailFrom { get; set; }
    public string EMailTo { get; set; }
    public string EMailSubject { get; set; }
    public string EMailBody { get; set; }
    public void SendEmail()
    {
        // здесь код для отправки письма
    }
}

public class Invoice
{
    public long InvoiceAmount { get; set; }
    public DateTime InvoiceDate { get; set; }

    private Logger fileLogger;
    private Email emailSender;
    public Invoice()
    {
        fileLogger = new Logger();
        emailSender = new Email();
    }

    public void AddInvoice()
    {
        try
        {
            // Здесь код для формирования счёт-фактуры
            // как только счёт-фактура сформирована - отправляем письмо
            emailSender.EMailFrom = "emailfrom@xyz.com";
            emailSender.EMailTo = "emailto@xyz.com";
            emailSender.EMailSubject = "SRP";
            emailSender.EMailBody = "У каждого программного модуля или класса должна быть только одна причина для изменения";
            emailSender.SendEmail();

           emailSender.SendEmail();
        }
        catch (Exception ex)
        {
            //записываем ошибку в лог
            fileLogger.AddLog(@"c:\ErrorLog.txt", ex.ToString());
        }
    }

    public void DeleteInvoice()
    {
        try
        {
            //Здесь код для удаления уже сформированной счёт-фактуры
        }
        catch (Exception ex)
        {
            //записываем ошибку в лог
            fileLogger.AddLog(@"c:\ErrorLog.txt", ex.ToString());
        }
    }
}

Теперь у каждого класса есть своя функциональность и, следовательно, возможные изменения классов будут иметь только одну причину изменения, что соответствует SRP.

Преимущества SRP

Ниже приведены преимущества применения принципа единственной ответственности в C#:

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