Single responsibility principle

When we write code, we are constantly building abstractions; when doing this, we are interested in building the right ones, delineated in the right way. The SRP helps us to figure out how to delineate these abstractions by looking at their responsibilities.

Responsibility, in this context, refers to the purpose and area of concern that your abstraction encompasses. A function that validates phone numbers can be said to have a singular responsibility. A function that both validates and normalizes those numbers with their country codes, however, can be said to have two responsibilities. The SRP would tell us that we need to split that abstraction into two separate ones.

The aims of the SRP are to arrive at code that is highly cohesive. Cohesiveness is when an abstraction's parts are all functionally united in some way, where they can all be said to work together to fulfill the abstraction's purpose. A useful question about discerning singular responsibility is: how many reasons does your abstraction's design have to change?

We can explore this question using an example. Say that we are tasked with building a small calendar application. We might imagine, initially, that there are two distinct abstractions here:

class Calendar {}
class Event {}

The Event class can be said to contain time and metainformation about an event, and the Calendar class can be said to contain events. The basic starting premise is that you can both add and remove one or more Event instances to and from a Calendar instance. Here, we express the methods used to add and remove events from Calendar:

class Calendar {
addEvent(event) {...}
removeEvent(event) {...}
}

Over time, we have to add various other pieces of functionality to our Calendar, such as methods for retrieving events within specific dates, and methods to export events in various formats:

class Calendar {

addEvent(event) {...}
removeEvent(event) {...}
getEventsBetween(stateDate, endDate) {...}

setTimeOfEvent(event, startTime, endTime) {...}
setTitleOfEvent(event, title) {...}

exportFilteredEventsToXML(filter) {...}
exportFilteredEventsToJSON(filter) {...}

}

Even without implementations, you can see how the addition of all of these methods has created a far more complex class. Technically, all of these methods are related to the functionality of a calendar, so there is an argument for them to remain within one abstraction, but if we go back to the question we posed—How many reasons does our abstraction's design have to change?—we can see that the Calendar class now has many possible reasons:

  • The way time is defined on events may need to change
  • The way titles are defined on events may need to change
  • The way events are searched for may need to change
  • The XML schema may need to change
  • The JSON schema may need to change

Given the number of different reasons for potential change, it makes sense to split the change into more appropriate abstractions. The methods for setting the time and title of a particular event (setTimeOfEvent, setTitleOfEvent), for example, definitely make sense within the Event class itself, as they're highly related to the purpose of the Event class: to contain details regarding a specific event. And the methods that export to both JSON and XML should also be moved, perhaps into their own class that is solely responsible for the export logic. The following code shows the changes that we've made: 

class Event {
setTime(startTime, endTime) {...}
setTitle(title) {...}
}

class Calendar {
addEvent(event) {...}
removeEvent(event) {...}
getEventsBetween(stateDate, endDate) {...}
}

class CalendarExporter {
exportFilteredEventsToXML(filter) {...}
exportFilteredEventsToJSON(filter) {...}
}

As you can hopefully see, each of our abstractions seems inwardly cohesive, and each one encapsulates its responsibilities far more cohesively than would be the case if all of that functionality resided solely within the Calendar class.

The SRP is not only about creating abstractions that are simple to use and maintain, it also allows us to write code that is more focused on its key purpose. Being more focused in this way gives us a clearer path to optimize and test our units of code, which benefits the reliability and efficiency of our codebase. The correct delineation of cohesive abstractions, guided by the SRP, is probably one of the most significant ways you can improve the cleanness of your code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.162.201