Single responsibility principle

Home Forums Programming Software engineering Object-oriented programming Single responsibility principle

This topic contains 0 replies, has 1 voice, and was last updated by  Васильев Владимир Сергеевич 3 months, 2 weeks ago.

  • Author
    Posts
  • #4092

    This article provides a short and clear description for one of the main code design rules (SOLID) – an Single responsibility principle. It is demonstrating examples in C# programming language.

    The Single responsibility principle ( hence the letter S in the acronym SOLID) claims: there must not be more than one reason for changing the class. This statement is not explaining much though, is it? I personally have a question on my mind when I hear it: in what cases can occur the reason to change a class?

    • If the class functionality is not enough
    • If the current functionality is becoming irrelevant
    • If you want to specify implementation details of individual parts of the algorithm implemented in class methods

    Now let’s see the example of code in which this SOLID principle of single responsibility is violated.

    Here is the class Calculator, which performs some calculations in the method Total. After performing the calculations, the method writes the results to MS Sql database , while still being logging information about all kinds of problems that could occur at a step to access the database.

    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(1,2,3);
            Console.ReadLine();
         }
    }

    It seems that everything in this code is how it should be. But what would happen if in some cases you needed to write the results not to MS Sql database , but, let’s say, to an XML file or just a database of a different provider?

    Or what if logging exception information also met the same problem?

    For example, if we tried to place all possible variants of writing the results and logging exceptions information directly within the class Calculator, the class would look very strange. But the most importan thing – we would have more than one reason to change the class:

    • computing
    • writing results
    • logging

    But the only necessary requirement for the class itself was to perform calculations. That means that Calculator class is only responsible for performing particular calculations, and this responsibility should be one and only, according to the first principle of SOLID.

    How to fix the code to match the single responsibility principle?

    We need to separate the algorithm of writing results of calculations and logging from the logic of calculations, and place it outside the Calculator class, withing other classes. We shall use interfaces ILog and IResultWriter that will define the logic for any way of writing results in the database and logging.

    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(1,2,3); 
          Console.ReadLine();
      }
     }

    So now the Calculator class is defined according to the single responsibility principle of SOLID, as it performs directly assigned task – calculating . As for ancillary and related activities (logging and writing the results), it uses other types which were defined specially for those purposes.

You must be logged in to reply to this topic.