The Single Responsibility Principle is about your code doing only 1 thing and you can split all functionality in several classes which all are meant for doing 1 specific thing.
An example is a specific class for validation, doing some business logic, enriching a model, retrieving data, updating data, navigation, etc.
The Separation of Concerns is about your code not being tightly coupled to some other classes/systems. Using interfaces in your code helps a lot, this way you can loosly couple classes/systems to you code. A plusside on this is that it's easier to unit-test your code also. There are a lot of (IoC) frameworks which can help you achieve this, but you can implement such a thing yourself also of course.
An example of something SoC, but not having SRP
public class Foo
{
private readonly IValidator _validator;
private readonly IDataRetriever _dataRetriever;
public Foo(IValidator validator, IDataRetriever dataRetriever)
{
_validator = validator;
_dataRetriever = dataRetriever;
}
public NavigationObject GetDataAndNavigateSomewhereIfValid()
{
var data = _dataRetriever.GetAllData();
if(_validator.IsAllDataValid(data))
{
object b = null;
foreach (var item in data.Items)
{
b = DoSomeFancyCalculations(item);
}
if(_validator.IsBusinessDataValid(b))
{
return ValidBusinessLogic();
}
}
return InvalidItems();
}
private object DoSomeFancyCalculations(object item)
{
return new object();
}
private NavigationObject ValidBusinessLogic()
{
return new NavigationObject();
}
private NavigationObject InvalidItems()
{
return new NavigationObject();
}
}
As you can see, this code isn't tightly coupled to classes or other systems, because it only uses some interfaces to do stuff. This is good from a SoC standpoint.
As you can see this class also contains 3 private methods which do some fancy stuff.
From a SRP standpoint, those methods should probably be placed within some classes of their own. 2 of them do something with navigation, which would fit in some INavigation class. The other does some fancy calculations on an item, this could probably be placed within an IBusinessLogic class.
Having something like this, you both have the SoC and SRP in place:
public class Foo
{
private readonly IValidator _validator;
private readonly IDataRetriever _dataRetriever;
private readonly IBusinessLogic _businessLogic;
private readonly INavigation _navigation;
public Foo(IValidator validator, IDataRetriever dataRetriever, IBusinessLogic businessLogic, INavigation navigation)
{
_validator = validator;
_dataRetriever = dataRetriever;
_businessLogic = businessLogic;
_navigation = navigation;
}
public NavigationObject GetDataAndNavigateSomewhereIfValid()
{
var data = _dataRetriever.GetAllData();
if(_validator.IsAllDataValid(data))
{
object b = null;
foreach (var item in data.Items)
{
b = _businessLogic.DoSomeFancyCalculations(item);
}
if(_validator.IsBusinessDataValid(b))
{
return _navigation.ValidBusinessLogic();
}
}
return _navigation.InvalidItems();
}
}
Of course you could debate if all this logic should be placed in the GetDataAndNavigateSomewhereIfValid
method. This is something you should decide for yourself. To me it looks like this method is doing way too much stuff.
The SRP states, in no uncertain terms, that a class should only ever have one reason to change.
Deconstructing the "report" class in the question, it has three methods:
printReport
getReportData
formatReport
Ignoring the redundant Report
being used in every method, it is easy to see why this violates the SRP:
The term "print" implies some kind of UI, or an actual printer. This class therefore contains some amount of UI or presentation logic. A change to the UI requirements will necessitate a change to the Report
class.
The term "data" implies a data structure of some kind, but doesn't really specify what (XML? JSON? CSV?). Regardless, if the "contents" of the report ever change, then so will this method. There is coupling to either a database or a domain.
formatReport
is just a terrible name for a method in general, but I'd assume by looking at it that it once again has something to do with the UI, and probably a different aspect of the UI than printReport
. So, another, unrelated reason to change.
So this one class is possibly coupled with a database, a screen/printer device, and some internal formatting logic for logs or file output or whatnot. By having all three functions in one class, you're multiplying the number of dependencies and tripling the probability that any dependency or requirement change will break this class (or something else that depends on it).
Part of the problem here is that you've picked a particularly thorny example. You should probably not have a class called Report
, even if it only does one thing, because... what report? Aren't all "reports" completely different beasts, based on different data and different requirements? And isn't a report something that's already been formatted, either for screen or for print?
But, looking past that, and making up a hypothetical concrete name - let's call it IncomeStatement
(one very common report) - a proper "SRPed" architecture would have three types:
IncomeStatement
- the domain and/or model class that contains and/or computes the information that appears on formatted reports.
IncomeStatementPrinter
, which would probably implement some standard interface like IPrintable<T>
. Has one key method, Print(IncomeStatement)
, and maybe some other methods or properties for configuring print-specific settings.
IncomeStatementRenderer
, which handles screen rendering and is very similar to the printer class.
You could also eventually add more feature-specific classes like IncomeStatementExporter
/ IExportable<TReport, TFormat>
.
This is made significantly easier in modern languages with the introduction of generics and IoC containers. Most of your application code does not need to rely on the specific IncomeStatementPrinter
class, it can use IPrintable<T>
and thus operate on any kind of printable report, which gives you all of the perceived benefits of a Report
base class with a print
method and none of the usual SRP violations. The actual implementation need only be declared once, in the IoC container registration.
Some people, when confronted with the above design, respond with something like: "but this looks like procedural code, and the whole point of OOP was to get us -away- from the separation of data and behavior!" To which I say: wrong.
The IncomeStatement
is not just "data", and the aforementioned mistake is what causes a lot of OOP folks to feel they are doing something wrong by creating such a "transparent" class and subsequently start jamming all kinds of unrelated functionality into the IncomeStatement
(well, that and general laziness). This class may start out as just data but, over time, guaranteed, it will end up as more of a model.
For example, a real income statement has total revenues, total expenses, and net income lines. A properly-designed financial system will most likely not store these because they are not transactional data - in fact, they change based on the addition of new transactional data. However, the calculation of these lines is always going to be exactly the same, no matter whether you are printing, rendering, or exporting the report. So your IncomeStatement
class is going to have a fair amount of behaviour to it in the form of getTotalRevenues()
, getTotalExpenses()
, and getNetIncome()
methods, and probably several others. It is a genuine OOP-style object with its own behaviour, even if it doesn't really seem to "do" much.
But the format
and print
methods, they have nothing to do with the information itself. In fact, it's not too unlikely that you'll want to have several implementations of these methods, e.g. a detailed statement for management and a not-so-detailed statement for the shareholders. Separating these independent functions out into different classes gives you the ability to choose different implementations at runtime without the burden of a one-size-fits all print(bool includeDetails, bool includeSubtotals, bool includeTotals, int columnWidth, CompanyLetterhead letterhead, ...)
method. Yuck!
Hopefully you can see where the above, massively-parameterized method goes wrong, and where the separate implementations go right; in the single-object case, every time you add a new wrinkle to the printing logic, you have to change your domain model (Tim in finance wants page numbers, but only on the internal report, can you add that?) as opposed to just adding a configuration property to one or two satellite classes instead.
Implementing the SRP properly is about managing dependencies. In a nutshell, if a class already does something useful, and you are considering adding another method that would introduce a new dependency (such as a UI, a printer, a network, a file, whatever), don't. Think about how you could add this functionality in a new class instead, and how you could make this new class fit into your overall architecture (it's pretty easy when you design around dependency injection). That is the general principle/process.
Side note: Like Robert, I patently reject the notion that an SRP-compliant class should have only one or two state variables. Such a thin wrapper could rarely be expected to do anything truly useful. So don't go overboard with this.
Best Answer
The concepts of Separation of Concerns, Cohesion, and the Single Responsibility Principal are all complimentary. The key is to understand that change, and more importantly enabling change without breaking other things, is the driving force behind the SRP. Ensuring your classes adhere to Separation of Concerns and are Cohesive all support this goal.
Separation of Concerns focuses on separating software into distinct sections/modules/components. The goal is modularity and code reuse.
Cohesion is an abstract measure of how well the data and methods of a class support the class's intended purpose in the system.
Robert Martin, towards the end of his blog post, elaborates on this relationship between the Single Responsibility Principal, Separation of Concerns and Cohesion:
The first paragraph in the quote above illustrates what I believe are his main motivations for the SRP — to change or fix something without breaking something else. He specifically mentions "separate concerns" and "cohesion" in this series of paragraphs, because you cannot create a system you can change safely without separating concerns and making sure your classes are cohesive.
Martin rephrases his own invention in a different way to emphasize the connection to change:
The goal of SRP is to design a system that can be changed without things devolving to bug whack-a-mole. In order to do that, you need to separate your concerns to create a modular application, and create cohesive classes, which provides a balance between separating things and keeping things together.
I've always had a hunch that the Single Responsibility Principal is the mortar that holds the "Separation of Concerns" brick together with the "Cohesion" brick. These three concepts are intertwined, but accomplish different goals in application design.
To see where these two concepts can result in different designs, consider situations where enabling change is desirable, but code reuse is not.
Let's say you have a data access class connected to a RDBMS. This involves two main concerns: interacting with the database and mapping query results to objects. By just considering separation of concerns, you could justify two different classes: a mapper and a SQL gateway. Now you want to swap out a SQL database for a web service. Since the web service returns different data structures than a SQL database, changing from a SQL gateway to a web service gateway requires you to change the mapper, too.
But is this separation useful? A change in one component requires a change in another component. Do you ever need to reuse the SQL mapping in other classes? The answers to these questions depend on the system design. If you need to reuse data mappings, then placing that logic in it's own class makes sense. If you only have a single class using this data mapping, and every time you change the gateway you need to change the mapper, then you gain nothing by separating those two concerns. This is where the single responsibility principal offers additional advice that can simplify the design:
Yes, it's the same quote mentioned above. Because it's the really important part.
If the mapper changes every time you change the gateway in your data access class, the SRP says, "then don't separate it!" The idea is if you don't separate these things, then you are less likely to introduce a bug when changing one of the components.
SoC says to separate things to make them reusable. SRP says to separate things when they change for different reasons, and don't separate things when they change for the same reason so you don't accidentally introduce a bunch of bugs.