Single Responsibility Principle

March 19, 2019

Single Responsibility Principle (SRP) is the first principle of SOLID, which is an acronym for five software design principles intend to make software more comprehendable, flexible, and maintainable. These principles are stepped forward by Robert C. Martin who is exceptionally concerned about clean software design.

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

"A class should have one, and only one, reason to change." is written for the definition of SRP at founder's website.

Even though this principle looks fairly simple to understand and accomplish, many times it is misunderstood by developers and used improperly. Therefore, SRP is considered as one of the simplest of the SOLID principles and yet hardest to get right.

Confusion Scenario: Injecting Many Dependencies

Martin Seemann, in his article, suggests grouping the injected dependencies into a wrapper class to reduce the services injected to the class, to comply with SRP. This indicates that SRP is misunderstood by him.

The injected services list can grow as much as needed, there should be no limit for it; as long as the class which uses these dependencies has still one reason to change and injected classes don't share a same single reason to change.

Even though this scenario doesn't violate SRP, it's highly unlikely to exist. If it does, chances are, there is something wrong with either

  • the over modularization of injected classes. It's like going on a fishing trip and bringing along the parts of the fishing pole like handle, reel seat, hook keeper etc. instead of the actual fishing pole. The classes with the same reason to change, should not be separated,
  • or the class that is being injected does not have a single reason to change. It's like going on a fishing trip and bringing along a fishing pole, tackle box, quilting supplies, bowling ball, nunchucks, flame thrower etc. If a class needs all the injections, then probably it does not have a single reason to change.

A Simple Problem

BankAccount class is being used for both checking and savings accounts of customers.

public abstract class BankAccount {
  void Deposit (double amount) {...}
  void Withdraw (double amount) {...}
  void AddInterest (double amount) {...}
  void Transfer (double amount, IBankAccount toAccount) {...}
}

When checking and savings account responsibilities of the program are changed individually, this class would have to change. This means, this class has more than one reason to change.

public class BankAccount {
  void Deposit (double amount) {...}
  void Withdraw (double amount) {...}
  void Transfer (double amount, IBankAccount toAccount) {...}
}

public class CheckingAccount : BankAccount {...}

public class SavingsAccount : BankAccount {
  void AddInterest (double amount) {...}
}

Creating two separate classes has solved the problem. Now each of the classes have only one reason to change.

Confusion with Modularity

SRP is often confused with modularity; although the principle may provide modularity, doesn't mean breaking down the software into its smallest pieces is the correct way of applying SRP. There is a reason for defining SRP as "A class should have one reason to change" instead of "A class should have one reason to exist".

If SRP is thought as dividing the system into the classes for each purpose, the code will be fragmented into its smallest parts without a reason. This would lead to

  • collapse of encapsulation; fields would have to be public to gain access among the classes,
  • cohesion would be broken; the related things that should have been inside same classes would be parted,
  • if not for anything, adds needless complexity by filling the system classes with single methods and few lines of code.

A class may have multiple responsibilities with lots of lines of code but all those responsibilities may change at the same time with a single reason.

When should you separate the classes?

SRP is about limiting the impacts of change; the system should be modularized according to likely sources of change.

A software designer certainly shouldn't wait to apply SRP until a change occurs, nor should overdo it and divide the parts of the fishing pole, while a fishing pole is needed as a whole. Therefore, applying SRP correctly is up to software designer's good judgement.

Sometimes science is more art than science.

When a change ensues in the system, if good judgement is applied and the functionalities separated beautifully according to the specific changes, then the change should have impact on that particular class only. This also removes the burden of retesting other affected classes.

Car Engine Example

Consider a car engine, as an example. Do you care about how exactly the engine is working? Do you care about the size of pistons? Do you care about how many cycles does camshaft do per second? Or do you only care about that the engine operates as intended when you get in the car?

Well, it depends on the context. If you are a mechanic who is able to fix problems in an engine, then you would know and care about the inner parts. When parts of the engine needs to change you can handle the change individually. This means in the future, there might be some changes on the inner parts.

But if you are a casual driver, who uses cars just for transportation, then you wouldn't need to know everything about the inner parts of the engine. This means you can't handle the changes of the individual parts, but must change the engine altogether. This means, in the future, changes on the inner parts are highly unlikely.

In either case, the context has determined what the appropriate separation of responsibilities is.

  • For the mechanic scenario it would be reasonable to have separate classes; since the inner engine parts are interchangeable individually.
  • In the latter scenario, where a casual driver takes place, it's clear that having an engine class that all the inner parts are grouped inside, is enough; since the change can only occur on engine level.

So the designer should decide if the classes should be arranged individually or kept as a whole, according to likely sources of change.

If many responsibilities are gathered inside a single class, but all these responsibilities may change with the same single reason, then there is nothing wrong with the class. However, if the class' responsibilities may change with different reasons then the class should be divided into particular parts.

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

Code Example: WinForms Application

Every now and then you have written or seen some code where SRP is violated. Like in the example below.

public partial class Form1 : Form {
  private void btnLoad_Click(object sender, EventArgs e)
  {
    listView1.Items.Clear();
    var sourceIndicator = txtsourceIndicator.Text;
    using (var fs = new FileStream(sourceIndicator, FileMode.Open))
    {
      var reader = XmlReader.Create(fs);
      while (reader.Read()) {
        if (reader.Name != "product") continue;
        var id = reader.GetAttribute("id");
        var name = reader.GetAttribute("name");
        var unitPrice = reader.GetAttribute("unitPrice");
        var discontinued = reader.GetAttribute("discontinued");
        var item = new string[]{id, name, unitPrice, discontinued};
        var lvItem = new ListViewItem(item);
        listView1.Items.Add(lvItem);
      }
    }
  }
}
Simple WinForms Application which Parses an XML File and Shows Items on Listview

Even though it's really likely to come across such code, it has some serious issues based on SRP. It is obvious that the class doesn't serve one purpose and these purposes wouldn't change with the same single reason.

The class has 4 responsibilities and all these responsibilities might change at different times, for different reasons:

  • 1) Manipulating listview.
  • listView1.Items.Clear();
    ...
    var item = new ListViewItem(new string[]{id, name, unitPrice, discontinued});
    listView1.Items.Add(item);
  • 2) Loading data from the data source.
  • using (var fs = new FileStream(sourceIndicator, FileMode.Open))
  • 3) Reading data from data source.
  • var reader = XmlReader.Create(fs);
    while (reader.Read()) {
  • 4) (implicit) Model mapping.
  • if (reader.Name != "product") continue;
    var id = reader.GetAttribute("id");
    var name = reader.GetAttribute("name");
    var unitPrice = reader.GetAttribute("unitPrice");
    var discontinued = reader.GetAttribute("discontinued");
    var item = new string[]{id, name, unitPrice, discontinued};

All of these can change independent of each other. In the future,

  • (1) content of the listview might change; new columns can be added or removed.
  • (2) data source might change, you might need to retrieve the data source from a database.
  • (3) type of the data source might change, to a json for instance.
  • (4) content of the file might change, new fields can be added.

Therefore, we need 3 more different classes to perform this process successfully in SRP standards. Let's start dividing the responsibilities into their own classes.

public interface ISourceProvider
{
   Stream Load(string sourceIndicator);
}
public class SourceProvider : ISourceProvider
{
   public Stream Load(string sourceIndicator)
   {
       return new FileStream(sourceIndicator, FileMode.Open);
   }
}
SourceProvider class to handle the possible future changes of loading from data source
public interface ISourceReader
{
    ISourceReader Create(Stream stream);
    string Name { get; }
    string GetAttribute(string attribute);
    bool Read();
}
public class SourceReader : ISourceReader
{
    private XmlReader reader;

    public string Name { get { return reader.Name; } }

    public ISourceReader Create(Stream stream)
    {
        reader = XmlReader.Create(stream);
        return this;
    }

    public bool Read()
    {
        return reader.Read();
    }

    public string GetAttribute(string attribute)
    {
        return reader.GetAttribute(attribute);
    }
}
Wrapper class (Adapter Pattern) for XmlReader to handle the possible future changes on data source type
public interface IProductMapper
{
   Product Map(ISourceReader reader);
}
public class ProductMapper : IProductMapper
{
   public Product Map(ISourceReader reader)
   {
      if (reader == null)
         throw new ArgumentNullException("Source reader is null");

      if (reader.Name != "product")
         return null;

      var product = new Product();
      product.Id = int.Parse(reader.GetAttribute("id"));
      product.Name = reader.GetAttribute("name");
      product.UnitPrice = decimal.Parse(reader.GetAttribute("unitPrice"));
      product.Discontinued = bool.Parse(reader.GetAttribute("discontinued"));

      return product;
   }
}
ProductMapper class to handle the possible future changes of model mapping
public class Form1 : Form {
   private readonly ISourceProvider sourceProvider;
   private readonly ISourceReader sourceReader;
   private readonly IProductMapper productMapper;

   public ProductRepository()
   {
      this.sourceProvider = new SourceProvider();
      this.sourceReader = new SourceReader();
      this.productMapper = new ProductMapper();
   }

   private void btnLoad_Click(object sender, EventArgs e)
   {
      listView1.Items.Clear();
      var sourceIndicator = txtsourceIndicator.Text;

      var products = GetProducts(sourceIndicator);

      foreach (Product product in products)
      {
         var item = new ListViewItem
         (
            new[]
            {
               product.Id.ToString(),
               product.Name,
               product.UnitPrice.ToString(),
               product.Discontinued.ToString()
             }
         );

         listView1.Items.Add(item);
      }
  }

  private IEnumerable GetProducts(string sourceIndicator)
  {
     var products = new List();
     using (Stream stream = sourceProvider.Load(sourceIndicator))
     {
        var reader = sourceReader.Create(stream);
        while (reader.Read())
        {
           var product = productMapper.Map(reader);
           if (product != null)
              products.Add(product);
        }
     }

     return products;
  }
}
Now our class has only one responsibility and that is manipulating the listview.

This is just a sample application and we assume all these responsibilities might change in the future. You might have this exact class in your project and if you're hundred percent sure that the separate responsibilities in the class all will change at the same time with the same reason, then you wouldn't need to (and in fact, you shouldn't) modify the class at all, in terms of SRP.

Again, SRP is all about making good judgement of the future of your project.

Check out the extended, complete, and concern-separated version of the example.

Benefits of SRP

Finally, even though throughout the article some of them were brought to light, I want to point out the advantages of SRP explicitly.

Unlike argued by many, SRP does not provide modularity directly; so we can't count the benefits of modularity as benefits of SRP. SRP may provide modularity indirectly which leads to more readable, easier to understand code because it is modularized; though we can't say these are the benefits of SRP. They are the benefits of modularity.

SRP increases the cohesion between things that change for the same reasons, and decreases the coupling between things that change for different reasons.

  • Changes in a class will require fewer changes in other modules: Impact of change is reduced.
  • Complexity is reduced when it comes to changing things. When a change is nigh, the class you should look for is already predetermined.
  • Since a change will affect the related class only, other classes won't need to be retested.