SRP: Single Responsibility Principle

Abu Jobaer
10 min readFeb 5, 2019

SRP stands for Single Responsibility Principle. It is one of the principles of Object-Oriented Design. Robert Cecil Martin¹ (widely known as Uncle Bob) introduces some principles in his famous book:

Agile Software Development, Principles, Patterns, and Practices

He describes five OOD principles in his book. They are collectively known as the SOLID Principles. These principles have received wide attention in the software industry. The Single Responsibility Principle is the first one of the five principles.

This principle was first described by Tom DeMarco² and Meilir Page-Jones³. They called it Cohesion⁴. Before knowing it, I want you to keep in mind:

Software is all about behavior.

Now we need to be clear about Cohesion. What is this? Cohesion measures the strength of the relationship between things that a module or a class contains. We know a class contains its methods (behaviors) and data. Cohesion refers to the strength or looseness of the relationship between the methods and data within a class.

A class or a module with high Cohesion is preferable. Because it makes code robust, reliable, reusable, scalable, maintainable, and understandable. These are very desirable traits in making an application. In contrast, low Cohesion makes code difficult to maintain, test, and understand.

Later, Uncle Bob in his book, shifts that meaning a bit and relates Cohesion to the forces that cause a module or a class to change. Now, we would explore SRP in the next sections.

Implementation

Have you really kept in mind that software is all about behavior? Okay! Say, we have been asked to build a simple web app through which users can contact the business owner. Here is what our contact app looks like:

// TypeScript

const contactData: ContactData = {
firstName: 'Abu',
lastName: 'Jobaer',
email: 'unclexo@example.com',
message: 'An example message.',
};

const contact = new Contact(contactData);

// Sends email if data are valid
if (contact.validate()) {
contact.sendEmail();
}
// PHP

$contactData = [
'firstName' => 'Abu',
'lastName' => 'Jobaer',
'email' => 'unclexo@example.com',
'message' => 'An example message.'
];

$contact = new Contact($contactData);

// Sends email if data are valid
if ($contact->validate()) {
$contact->sendEmail();
}

In the above script, we see user data (assuming we get them from a submitted form). Then we create a contact object using a class called Contact bypassing user data in its constructor method. After that, we call the validate() method to validate user data and sendEmail() to send emails.

This is what we need for our contact app.

Now we need to make the Contact class. According to the app, our Contact class will be as the following:

// TypeScript

class Contact {
private data: ContactData;

public constructor(data: ContactData) {
this.data = data;
}

public validate(): boolean {
// write code for validating user data

return true;
}

public sendEmail(): void {
// write code for sending email
}
}

interface ContactData {
firstName: string;
lastName: string;
email: string;
message: string;
}
// PHP

class Contact
{
private $data;

public function __construct(array $data)
{
$this->data = $data;
}

public function validate()
{
// write code for validating user data

return true;
}

public function sendEmail()
{
// write code for sending email
}
}

Now we have our Contact class ready for our contact app. By the way, did you notice what behaviors does this class have? In the context of a class, methods are behaviors. Behaviors perform different tasks for the class. So we have three behaviors because we have three methods in our class. Keep them in mind.

We would start looking into our Contact class in the context of the Single Responsibility Principle. We need to involve ourselves to figure out the meaning of the word responsibility. Otherwise, R of SRP.

What is our class responsible for?

Our Contact class has an attribute that holds user data. Then we have a constructor method where we initialize user data. Next, we have a couple of methods. As we are dealing with user data, we must validate those data. Because users can put in some malicious data that we do not want to accept. So we have another method for data validation. We have yet another method for sending emails.

Notice these methods are responsible for doing specific jobs. Now we would go through each and every responsibility. Check out below:

1st Responsibility

The first responsibility of the class is to create a contact object. Well, this is done by the constructor method. It also initializes user data. When we create the contact object, behind the scene, the constructor method is called and initializes user data.

2nd Responsibility

The second responsibility is to validate user data. The validate() method is responsible for validating user data. Look at the method below:

// TypeScript

public validate(): boolean {
// Assume validator API is imported
const validator = new Validator();

validator.set(this.data.firstName, 'string|max:45|notEmpty');
validator.set(this.data.lastName, 'string|max:45|notEmpty');
validator.set(this.data.email, 'email|notEmpty');
validator.set(this.data.message, 'string|max:200');

return validator.validate();
}
// PHP

public function validate()
{
// Assume validator API is imported
$validator = new Validator();

$validator->set($this->data['firstName'], 'string|max:45|notEmpty');
$validator->set($this->data['lastName'], 'string|max:45|notEmpty');
$validator->set($this->data['email'], 'email|notEmpty');
$validator->set($this->data['message'], 'string|max:200');

return $validator->validate();
}

Here we are assuming we have a validation API. We are validating user data using this API. So the main responsibility of this method is to perform a validation job with the help of the validation API.

3rd Responsibility

The third responsibility of the class is to send emails. The sendEmail() method is responsible for sending emails. Look at the method below:

// TypeScript

public sendEmail(): void {
// Assume Email API is imported
const mail = new Mail();

mail.from(this.data.email, this.data.lastName);
mail.to('business.owner@example.com', 'Fakira');
mail.subject('From Contact App');
mail.body(this.data.message);

mail.send();
}
// PHP

public function sendEmail() {
// Assume Email API is imported
$mail = new Mail();

$mail->from($this->data['email'], $this->data['lastName']);
$mail->to('business.owner@example.com', 'Fakira');
$mail->subject('From Contact App');
$mail->body($this->data['message']);

$mail->send();
}

Like validation API, we are assuming we also have an email API for sending emails.

Now we get the responsibilities of our class. Therefore, the Contact class has 3 responsibilities. Now we would see what is a responsibility in the context of SRP. Before looking at that, we need to take a look at the definition of SRP.

Definition

Uncle Bob gives a definition of the Single Responsibility Principle in his book. Here it is:

‘’A class should have only one reason to change.’’

The definition of SRP is very easy but might be confusing. If you are like me, you may think of it this way:

If a class has more than one responsibility, you need to apply SRP onto the class.

This is what the definition says. Uncle Bob says each responsibility is an axis of change. So it is important to understand what a responsibility is. Now take a look at that.

What is a responsibility?

Uncle Bob says a responsibility is “a reason for change” in the context of SRP. So we may write the definition of the SRP for the explanation purposes only as below:

The understanding of the single responsibility principle

This means a class should have only one responsibility, not more. What if a class has more than one responsibility? What is the problem then? If a class has more than one responsibility, then the responsibilities become coupled with the class. This means that changes to one responsibility can have a domino effect on the other classes. Therefore, one class should not depend on the way others work. If this is the case, then what about our class?

What about our class?

We have seen three responsibilities in our class. This means the Contact class has more than one responsibility. Among them, the validation and the sending of emails are noteworthy. So the Contact class has two reasons to change. These break the SRP. These are reasons to change the class. That means we have two reasons to apply SRP to our class. Why?

What if the validation API changes the notEmpty validation rule to the required key to give it a more specific name?

Well, we have to go back to our Contact class and replace notEmpty with required in all places in the validate() method. That means changes to the validation API affect the Contact class.

Likewise, if any changes happen in the mail API used in thesendMail() method, that change would make our Contact class change again.

So, it violates SRP.

What is NOT a responsibility?

If you think that each method of a class is a responsibility, then you are not on the right track. If so, you would get a class with the constructor method only, each time you apply SRP onto your class. We could have had more methods in our Contact class. For example:

  • getData() method to get user-provided data.
  • setData() method to set user-provided data.
  • and more…

The getData() would have been an accessor method to return user data while setData() would have been a mutator method to set user data for the data attribute. These methods would not be SRP’s responsibilities.

It is the perfect time to remember Cohesion here. Remember? If we had these methods in our class, that would have been a strong relationship between these two methods and data. Why? Because these methods would deal with only data but not with other contexts.

Think of how relevant are those methods in the context of data and class. For example, the context of our Contact class is all about preparing contact data (in an object-oriented approach) but is not validating data or sending emails. These are totally different contexts.

Eventually!

I said we have three responsibilities in our Contact class. But now, we know what a responsibility is. So the 1st responsibility is not a responsibility in our class in the context of SRP. I just used it for the demonstration purpose. But two other responsibilities are right. Any change to one of them makes the Contact class change. So we have to apply SRP to our Contact class. Now take a look at how our client code for the contact app should look like:

// TypeScript

const contactData: ContactData = {...};
const contact = new Contact(contactData);

// Prepares validator object
const validator = new Validator();
validator.setData(contact.getData());
validator.setRules(/* set validation rules */);

// Prepares mail object
const mail = new Mail();
mail.from(contact.email, contact.lastName);
mail.to('business.owner@test.com', 'Contact App Owner');
mail.subject('Contact App');
mail.body(contact.subject);

// Sends email if data are valid
if (validator.validate()) {
mail.send();
}
// PHP

$contactData = [...];
$contact = new Contact($contactData);

// Prepares validator object
$validator = new Validator();
$validator->setData($contact->getData());
$validator->setRules(/* set validation rules */);

// Prepares mail object
$mail = new Mail();
$mail->from($contact->email, $contact->lastName);
$mail->to('business.owner@test.com', 'Contact App Owner');
$mail->subject('Contact App');
$mail->body($contact->subject);

// Sends email if data are valid
if ($validator->validate()) {
$mail->send();
}

Notice we separate validation and mail APIs from the Contact class to the client code. Our Contact class does not depend on them anymore. The class is now quite independent. It has now only one responsibility which is preparing data:

// TypeScript

class Contact {
private data: ContactData;

constructor(data: ContactData) {
this.data = data;
}

getData(): ContactData {
return this.data;
}

get firstName(): string {
return this.data['firstName'];
}

get lastName(): string {
return this.data['lastName'];
}

get email(): string {
return this.data['email'];
}

get message(): string {
return this.data['message'];
}
}
// PHP

class Contact
{
private $data;

public function __construct(array $data)
{
$this->data = $data;
}

public function getData()
{
return $this->data;
}

/**
* Overloads properties
*
* Using this method we can use: $contact->firstName
*/
public function __get(string $name)
{
return $this->data[$name];
}
}

We remove validate() and sendEmail() methods. We add extra methods to make our app work. We add an accessor method getData() to get user data. We add other methods to access data via attributes.

One important thing is to NOTE that this Contact class is not a production-ready class and does not fit all. But you can play around with it. The design of a class depends on the application you are working with.

Now our Contact class is conforming to SRP. It has also a high Cohesion. This means the Contact class can easily be testable. To prove it you can test both Contact classes — old and new ones. You will understand the difference then. This can be reused across the app. Cast this Contact class to your next developers. They do not have to deal with data validation and sending emails but only prepare contact data. I think you get the idea.

How to FIND responsibilities?

Well, you already know what behaviors are in the context of a class: methods. Try to figure out tasks that have been assigned to methods. This technique will give you some idea. Otherwise, you may write a doc block for every method describing its purpose. This is absolutely important to get the responsibilities from a class.

Keep in mind, together a set of methods can represent a single responsibility instead of multiple responsibilities.

Try to understand whether a method has any dependency. Dependencies may become different contexts in the context of your class. Try to change any contexts you are dealing with. Then try to see whether that change affects other classes. If it does, you have got the responsibility. Apply SRP to that class.

Summary

Let us recap! We tried to understand what SRP is. To grasp SRP we needed to find out the responsibilities of the Contact class in the context of SRP. We found more than one responsibility in our class that violates SRP. That is why we applied SRP to our class to separate those responsibilities from our class.

Do you really need to apply SRP to a class? It depends on how your class or module is changing. If you find more than one responsibility in your class and changes to one responsibility do not affect other classes, then you do not need to apply SRP to a class. Finally, I recommend that practice what Uncle Bob said:

‘’Finding and separating those responsibilities from one another is much of what software design is really about.’’

Thanks for reading

--

--

Abu Jobaer

Advocate Clean Code, SOLID Principles, Software Design Patterns & Principles, and TDD.