Select Page
A class should only have one responsibility and a class should only have one reason to change.

SRP is trying to enforce high cohesion and loose coupling in our classes, our classes should only contain elements that are closely related. Having unrelated elements in our classes leads to tight coupling and low cohesion which makes our software fragile and hard to maintain.

As an example

public class Program
{
  static void Main(string[] args)
  {
    Person person = new Person();
    person.FirstName = "Joseph";
    person.LastName = "Smith";
    person.SayName();
    Console.ReadLine();
  }
}

public class Logger
{
  public void Log(string log)
  {
    Console.WriteLine(log);
  }
}

public class Person
{
  private Logger logger;

  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Person() {
    this.logger = new Logger();
  }

  public void SayName() {
    this.logger.Log($"{FirstName} {LastName}");
  }
}

Output:

Joseph Smith

The Person class set an instance variable to the value of a new Logger object from within its constructor, creating a dependency on the Logger class. Doing this has tightly coupled the Person class to the Logger class, now if the Logger class changes the way it logs strings, the Person class may need to change as well, as the Logger class may no longer log strings the way the Person class expects it to.

We can remove this dependency on the Logger class by having it implement an interface, and passing an instance of the Logger class into the constructor of the Person class.

public class Program
{
  static void Main(string[] args)
  {
    Person person = new Person(new Logger());
    person.FirstName = "Joseph";
    person.LastName = "Smith";
    person.SayName();
    Console.ReadLine();
  }
}
 
public interface ILogger
{
  void Log(string log);
}

public class Logger:ILogger
{
  public void Log(string log)
  {
    Console.WriteLine(log);
  }
}

public class Person
{
  private ILogger logger;

  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Person(ILogger logger) {
    this.logger = logger;
  }

  public void SayName() {
    this.logger.Log($"{FirstName} {LastName}");
  }
}

Output:

Joseph Smith

Now the Person class in no longer dependent on the implementation of the Logger class. If the Logger class no longer logs strings the way the Person class requires, we can create a new Logger class that implements ILogger interface that meets the requirements for logging a Person object, and pass an instance of that class into the constructor of the Person class.

public class Program
{
  static void Main(string[] args)
  {
    Person person = new Person(new AwesomeLogger());
    person.FirstName = "Joseph";
    person.LastName = "Smith";
    person.SayName();
    Console.ReadLine();
  }
}
 
public interface ILogger
{
  void Log(string log);
}

public class Logger:ILogger
{
  public void Log(string log)
  {
    Console.WriteLine(log);
  }
}

public class AwesomeLogger : ILogger
{
  public void Log(string log)
  {
    Console.WriteLine($"{log} is awesome!!");
  }
}

public class Person
{
  private ILogger logger;

  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Person(ILogger logger) {
    this.logger = logger;
  }

  public void SayName() {
    this.logger.Log($"{FirstName} {LastName}");
  }
}

Output:

Joseph Smith is awesome!!

Now we have successfully applied the Single Responsibility Principle to all three classes, giving them high cohesion, with a single responsibility, and only one reason to change.