Three Tiers or Why do it the Easy Way if you can do it more Complicated? SOLID – O: Open Closed Principle
Jul 18

SOLID is a combination of five important design principles:
SSingle Responsibility Principle
OOpen Closed Principle
LLiskov Substitution Principle
IInterface Segregation Principle
DDependency Inversion Principle

This post is about the Single Responsibility Principle.

Definition:

The single responsibility principle states that there should only be one reason for a class to change. To achieve that, every class should have a single responsibility. Thereby, only if something with this one responsibility changes, the class should have to change.

What are the benefits?

Reuse - If all your classes follow the srp, you are more likely to reuse some of them
Clarity - Your code is cleaner as your classes don’t do unexpected things
Naming - As all your classes have a single responsibility, choosing a good name is easy
Readability - Due to the clarity, better names and shorter files the readability is improved greatly.

What is a responsibility?

That is actually the part most have a problem with. By definition, every responsibility is a reason to change.
A responsibility can literally be everything, take for example a customer class, its responsibility is to represent a customer but not how it is loaded from a db or written to it nor is it its responsibility to print a revenue report.

A bad example:

public class Customer
{
    private Guid _id;

    private IList<Order> _orders;

    public Customer(Guid id)
    {
        LoadFromDb(id);
        _id = id;
    }

    public Customer() { }

    public Guid Id
    {
        get { return _id; }
    }

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

    public IEnumerable<Order> Orders
    {
        get { return _orders; }
    }

    private void LoadFromDb(Guid id)
    {
        using (var con = new SqlConnection())
        {
            using (var cmd = con.CreateCommand())
            {
            	//......
            }
        }
    }

    public void Save()
    {
        if (_id == Guid.Empty)
        {
            Insert();
        }
        else
        {
            Update();
        }
    }

    private void Update()
    {
        using(var con = new SqlConnection())
        {
            using(var cmd = con.CreateCommand())
            {
                //Update
            }
        }
    }

    private void Insert()
    {
        using(var con = new SqlConnection())
        {
            using(var cmd = con.CreateCommand())
            {
                //Insert
            }
        }
    }

    private void PrintRevenueReport()
    {
        // Reportlogic....
    }

    public override string ToString()
    {
        return string.Format("{0}, {1}", LastName, GivenName);
    }
}

This class has to change if either the representation of a customer changes, the database is changed or the layout of a report is changed.

Extracting the additional responsibilites

If we remove the parts that have nothing to do with the representation of a customer we are left with that:

public class Customer
{
    private Guid _id;

    private IList<Order> _orders;

    public Customer(Guid id)
    {
        _id = id;
    }

    public Customer() { }

    public Guid Id
    {
        get { return _id; }
    }

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

    public IEnumerable<Order> Orders
    {
        get { return _orders; }
    }

    public override string ToString()
    {
        return string.Format("{0}, {1}", LastName, GivenName);
    }
}

As we have to change the Id after an insert and that is no longer inside this class, it shouldn’t be read-only anymore. Further, we can remove the Constructor that requires the id as the class doesn’t care about it any more than about its other properties.

What is left

public class Customer
{
   private IList<Order> _orders;

    public Guid Id { get;set; }
    public string GivenName { get; set; }
    public string LastName { get; set; }

    public IEnumerable<Order> Orders
    {
        get { return _orders; }
    }

    public override string ToString()
    {
        return string.Format("{0}, {1}", LastName, GivenName);
    }
}

Now, it only has to change if the representation of a customer in our application changes. An likely example would be the requirement for an address.

What about the missing parts?

We simply create four interfaces, ICustomerPersister and ICustomerLoader, ICustomerReporter, IReportPrinter:

public interface ICustomerPersister
{
    void Persist(Customer customer);
}

public interface ICustomerLoader
{
    Customer LoadById(Guid id);
}

public interface ICustomerReporter
{
    CustomerReport CreateReportFrom(Customer customer);
}

Public interface IReportPrinter
{
    void PrintReport(Report report);
}

If updating or inserting has additional tasks (notification mail on registration etc.) you shouldn’t implement it all in the persister, instead you should make updating and inserting separate responsibilities and thereby classes. In that case, the persister would just become a facade.

Conclusion

Testing in isolation is now possible as is testing without having to hit any external resources like databases or printers. Additionally, by allowing multiple customers to be passed to the persistence method performance could be increased.

We do now have 5 classes and 4 interfaces for what a single class did before. Some fear that it’s now more complicated to use it as we have to instantiate way more classes. But you shouldn’t argue that way if you take the advantages into consideration. In the last part of this series, you will see that there are many IoC containers that happily take the responsibility of object instantiation away from you.

What about maintenance / code evolution?

Be aware that you have to have an eye on your classes, as time goes by, your classes will collect additional responsibilities that should immediately be refactored.

Developers that are not used to the srp might need some help if they first encounter such a project. They usually feel overwhelmed by the amount of classes / files .

Bookmark and Share

10 Responses to “SOLID – S: Single Responsibility Principle”

  1. SOLID – S: Single Responsibility Principle | Coding Efficiency…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  2. [...] Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface [...]

  3. rtpHarry says:

    Hey, I have just read this article and I wanted to tell you it feels a bit rushed. Please slow down and explain the concepts in more detail. I have read through an agile book and a design patterns book so I am aware of these concepts but need reminding of the details.

    You haven’t gone over what to do with the interfaces and how they go together with customer. Does it implement the interface or contain a concrete class or something?

  4. Sebastian says:

    @rtpHarry
    Hi,

    you could implement each of those interfaces in its own class. In this case, you’d probably need some kind of customer mapper which would be responsible for mapping your customer objects to data records and vice versa.

    Saving, loading etc. would no longer be handled by the customer object itself, instead these new classes would have to be called.

    Doing so would allow you to switch the database or use an orm without touching the customer object. Changes to the report wouldn’t effect the customer object either.

  5. [...] Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface [...]

  6. 9eFish says:

    SOLID – S: Single Responsibility Principle | Coding Efficiency…

    9efish.感谢你的文章 – Trackback from 9eFish…

  7. [...] Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface [...]

  8. [...] a comment Go to comments SOLID is a combination of five important design principles: S – Single Responsibility Principle O – Open Closed Principle L – Liskov Substitution Principle I – Interface Segregation [...]

  9. Lijo says:

    Hi,

    This is a unique and very effective post. Thanks. I would like to know is there any C# book that covers how to use SOLID using Generics. Is there any C# book that will explain all the best practices with language features like LAMBDA, Generics, etc.?

    Thanks

  10. [...] Single Responsibility Principle (SRP) – A classe deve ter uma única responsabilidade, uma única razão de existir. [...]

Leave a Reply

preload preload preload