Принцип единственности ответственности

      Комментарии к записи Принцип единственности ответственности отключены

Помечено: , ,

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 3 нед., 5 дн. назад.

  • Автор
    Сообщения
  • #4028

    Принцип единственности ответственности (Single responsibility principle, отсюда и буква S в аббревиатуре SOLID) гласит: не должно существовать более одного мотива для изменения класса. Формулировка более чем загадочная, не так ли? Лично у меня в связи с этим возникает вопрос: а в каких случаях может возникать причина для изменения класса?

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

    Рассмотрим пример кода, в котором данный принцип SOLID о Единственной ответственности нарушен.

    Здесь представлен класс Calculator, который выполняет некие вычисления в методе Total. Выполнив вычисления, метод записывает результаты в базу данных MS Sql, при этом ещё и ведётся логирование информации о всевозможных проблемах, которые могли произойти а этапе обращения к базе данных.

    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.Configuration;  
    // class that performs the calculations 
    class Calculator
    {
        public void Total (params double [] arr) 
        {    
            double result = 0;      
            // performing very difficult and important calculations 
            // ...       
            // writing results of calculations to MS Sql database
            string query = "INSERT INTO blah blah blah...";    
            SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["myConnection"].ConnectionString);   
            SqlCommand cmd = new SqlCommand(query, cn);  
            try
            {      
                cn.Open();   
                cmd.BeginExecuteNonQuery((r) => { ((SqlConnection)r.AsyncState).Close(); }, cn);
            }     
            catch (SqlException ex)    
            {      
                //Logging Sql exception information     
                //...    
            }
        } 
    }
    class Program
    {   
        static void Main() 
        {   
            Calculator calc = new Calculator();
            calc.Total();   
        }
    }

    Кажется, что всё в порядке. Но что будет, если в каких-то случаях понадобится записывать результаты не в базу данных MS Sql, а , скажем, в XML файл или просто в другую базу данных другого поставщика?

    А что, если логирование информации об исключениях тоже постигнет подобная проблема?

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

    • вычисления
    • запись результатов
    • логирование

    А ведь всё, что непосредственно требовалось от класса – выполнить вычисления. То есть на классе Calculator лежит ответственность за выполняемые вычисления, и эта ответственность должна быть единственной, согласно первому принципу SOLID.

    Как исправить код, чтобы он соответствовал принципу единственной ответственности?

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

    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.Configuration;
     
    // abstraction 
    interface ILog
    {
        void LogData(string text);
    }
     
    // implementation of abstraction 
    class DBLogger : ILog
    {
        public void LogData(string text)
        {
           // Logging to database 
           // ... 
        }
    }
     
    interface IResultWriter
    {
        void Write(double result, ILog log);
    }
     
    // implementation of abstraction 
    class DBResultWriter : IResultWriter
    {
        public void Write(double result, ILog log)
        {
           // Writing to database 
           // ... 
         //Logging
          //...
       }
    }
     
     
    // class that performs the calculations 
    class Calculator
    {
        ILog logger;
        IResultWriter writer;
     
        public Calculator(IResultWriter res, ILog log)
        {
             logger = log; 
             writer = res;
        }
     
         public void Total(params double[] arr)
        {
             double result = 0;
     
            // performing very difficult and important calculations 
            // ...
     
           // writing results of calculations to MS Sql database with log 
            writer.Write(result, logger);
     
        }
    }
    class Program
    {
       static void Main()
       {
          DBResultWriter writer = new DBResultWriter();
          DBLogger logger = new DBLogger();
          Calculator calc = new Calculator(writer, logger);
           calc.Total();
       }
    }

    Таким образом,теперь класс Calculator отвечает принципу единственной ответственности (Single responsibility principle) SOLID, поскольку сам он выполняет непосредственно возложенную задачу – вычисления, а для вспомогательных и сопутствующих действий (запись результатов и логирование) он задействует специально выделенные для этого типы.

Для ответа в этой теме необходимо авторизоваться.