Photo by Miguel Á. Padriñán

SOLID: Single Responsibility Principle

Paul Allies
3 min readJun 26, 2022

The Single Responsibility Principle (SRP) is one of the five principles of object-oriented programming known as SOLID. The SOLID principles were first defined by Robert C. Martin as a set of guidelines for writing maintainable, scalable, and robust software.

The SRP states that a class or function should have one, and only one reason to change. In other words, a class or function should have a single, well-defined role. (A role is a set of connected behaviours).

There are 5 principles that make up the acronym.

In this post, I will attempt to explain the SRP (Single Responsibility Principle).

In OO, classes are used to define the template for objects, while modules are used to organise and reuse code. In most (modern) languages, classes can also be used as modules where it is defined in a separate file and then imported and used in another file. Class and module will be used interchangeably from now on.

The Single Responsibility Principle is all about creating modules with one and only one role. You would know to use the Single Responsibility Principle (SRP) when you find yourself working on a module that has multiple roles or that is difficult to understand, test, and maintain.

Some signs that a module may be violating the SRP include:

  • The module is large and complex
  • The module has multiple responsibilities that are not closely related
  • Changes to the module have unexpected side effects in other parts of the system

Let’s illustrate this with an example. Let’s create a user repository class to be used throughout our application.

interface User {
id: number
name: string
emailAddress: string
}

interface IUserRepository {
getAllUsers(): User[]
createUser(user: User): void
deleteUser(id:number): void
updateUser(id: number, data: User): void
sendEmailTo(user: User, message: string, subject: string): void
}

class UserRepository implements IUserRepository {

constructor(userDataSource, emailService){
this.userDataSource = userDataSource
this.emailService = emailService
}

getAllUsers(){
return this.userDataSource.getAll()
}

createUser(user: User){
this.userDataSource.create(user)
}

deleteUser(id: number){
this.userDataSource.delete(id)
}

updateUser(id: number, data: User){
this.userDataSource.update(id, data)
}

sendEmailTo(user: User, message:string, subject: string){
this.emailService.send(user.emailAddress, message, subject)
}
}

Although this may seem fine, it violates the SRP. The module has two responsibilities: data source communication and sending emails. For this purpose, we should create another interface and implementation class which will handle communication.

interface User {
id: number
name: string
emailAddress: string
}

interface IUserRepository {
getAllUsers(): User[]
createUser(user: User): void
deleteUser(id:number): void
updateUser(id: number, data: User): void
}

interface IMessageRepository {
sendEmailTo(user: User, message: string, subject: string): void
}

class UserRepository implements IUserRepository {
constructor(userDataSource){
this.userDataSource = userDataSource
}

getAllUsers(){
return this.userDataSource.getAll()
}

createUser(user: User){
this.userDataSource.create(user)
}

deleteUser(id: number){
this.userDataSource.delete(id)
}

updateUser(id: number, data: User){
this.userDataSource.update(id, data)
}

}

class MessageRepository implements IMessageRepository {

constructor(emailService){
this.emailService = emailService
}

sendEmailTo(user: User, message:string, subject: string){
this.emailService.send(user.emailAddress, message, subject)
}
}

In Conclusion,

Through the SRP you code now becomes easier to understand, maintainable and flexible. We break down complex code into smaller focused pieces. When a module needs to now change it is less likely to affect other parts of code unrelated to it’s role.

This article was originally published on Nanosoft

--

--