SOLID is a combination of five important design principles:
S – Single Responsibility Principle
O – Open Closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency 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 .
SOLID – S: Single Responsibility Principle | Coding Efficiency…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
[...] 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 [...]
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?
@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.
[...] 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 [...]
SOLID – S: Single Responsibility Principle | Coding Efficiency…
9efish.感谢你的文章 – Trackback from 9eFish…
[...] 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 [...]
[...] 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 [...]
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
[...] Single Responsibility Principle (SRP) – A classe deve ter uma única responsabilidade, uma única razão de existir. [...]