Single-responsibility principle
computer-programming principle that states that every class in a computer program should have responsibility over a single part of that program's functionality, which it should encapsulate
The Single Responsibility Principle () is a fundamental software design principle stating that a class or module should have only one reason to change, meaning it should be responsible for a single aspect of functionality. This principle, introduced by Robert C. Martin as part of the SOLID principles, emphasizes that responsibilities should be separated based on why they change; for instance, a report generator should be split into separate classes for content compilation and formatting, as these aspects change for different reasons. By adhering to SRP, code becomes more maintainable, robust, and easier to test, as changes to one responsibility do not inadvertently affect others. The principle is rooted in the concept of cohesion and separation of concerns, aiming to reduce complexity and improve code clarity.
solid - What exactly is the single responsibility principle? - Software Engineering Stack Exchange
Difference between Single Responsibility Principle and Separation of Concerns - Stack Overflow
c# - How to approach Single Responsibility Principle? - Stack Overflow
Single Responsibility Principle vs Don't Repeat Yourself?
Because they touch different aspects of the coding.
The DRY principle suggests to not write the same lines more than once, so basically if you need something more than once just write it in a function and simply call it everytime you need. This because later on you'll have to simply change the function to affect all its calls.
The single responsability suggests that a function should do just one thing. So basically if your function does two things, split that in two separate functions.
EDIT: A personal note: I'm a beginner and that is what I understood of the two principles. If what I said is correct that would be a huge accomplishment in my learning :D
More on reddit.comVideos
The Single Responsibility Principle is confusingly named and vaguely defined. Robert Martin who first named the principle has revised and clarified its meaning multiple times, but it still causes plenty of confusion. That said, neither of the two definitions you give are even close.
Initially, the principle was defined as "A class should have only one reason to change", where change refers to changes in the source code.
A responsibility is an "axis of change", i.e. it represents a business requirement that may change over time, making it necessary for developers to change the code.
Of course this just raises the question of how to identify such an "axis of change". In later elaborations, Robert Martin has focused on the different actors or stakeholders that may cause requirement changes - e.g. different departments in an organization.
For example, if you have a module that calculates tax and draws the company logo, you have a violation of the principle, since the tax code likely will change independently of the logo design, since these are decided by different organizations or departments.
Of course, the principle should not be taken too literally. It is common in software development to have a single "product owner" who decides what goes into the product. But you have to consider the deeper causes behind the changes. This means a developer cannot just blindly implement a list of requirements received from above. The developers also need to understand the reasons for the requirements and what might cause them to change.
A more practical approach is to observe what parts of a system change over time, and separate the things that change often from things that remain stable.
The principle does not say that a class should only do "one thing", which is a nonsensical principle anyway - what does "one thing" even mean?
- Read the blog post kindly linked by @Greg Burghardt
- Ignore, for now, most everything Uncle Bob writes.
- Focus on the 1972 text by David Parnas, especially the parts that Uncle Bob highlights.
We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.
Then go back and read Bob's text. The principle is about decisions. Decisions are made by people. And it will be clearer. I think he emphasizes company management too much - many technical decisions are made by tech leads. But the principle remains. If something was a difficult decision, such as REST vs GraphQL, or likely to change, such as starting with SQLite and realizing that if the product does great you will need a different DB, then that decision should be isolated, so that changing that decision only affects a few classes, not the entire code base.
Single Responsibility Principle (SRP)- give each class just one reason to change; and “Reason to change” == “responsibility”. In example: Invoice class does not have a responsibility to print itself.
Separation of Concerns (since 1974). Concern == feature of system. Taking care of each of the concerns: for each one concern, other concerns are irrelevant. Hiding implementation of behavior.
From here.
The Single Responsibility Principle and Separation of Concerns are really the same thing.
Sure, you can get bogged down in an academic discussion trying to tease out some kind of difference between the two, but why? For all intents and purposes, they describe the same thing. The biggest problem is people get so caught up in wanting to know exactly what a "concern" and "responsibility" are, that they perhaps miss the important idea behind SRP and SoC.
That idea is simply to split your codebase into loosely coupled, isolated parts. This allows multiple developers to work on different parts without affecting each other, it also allows a single developer to modify one isolated part without breaking another.
This is applied at the module level, e.g. MVC is an architectural pattern promoting SRP and SoC. The codebase is split out into isolated models, views and controllers. That way, the modification of a view can be done independently of a model. The two aren't horrifically intertwined.
At a lower level, this should be applied to classes too. Instead of putting dozens of methods in a single class, split the code out into several. For the same reasons.
Also, even at a method level, split large methods out into smaller methods.
In principle. SRP is a principle, not a rule, so you don't have to (read: can't/shouldn't) follow it religiously to the extreme. It doesn't mean going too far and having only one seven line method in each class, for example. It just means a general principle of splitting out code into isolated parts. The point is it will lead to a better codebase and more stable software.
You are abstracting over the value passedInt. This is not the right approach. You must split the functional responsibilities. Here I can detect 3 responsibilities:
- Multiply (i.e., calculate)
- Writing to the console (i.e., logging in the broadest sense)
- Organizing and combining calculations and logging.
Therefore I declare 3 interfaces describing these 3 requirements:
public interface ICalculator
{
int Multiply(int x, int y);
}
public interface ILogger
{
void Log(string message);
void Close();
}
public interface IFactoriser
{
void DoFactorTen(int value);
}
Here is a possible implementation:
public class Calculator : ICalculator
{
public int Multiply(int x, int y)
{
return x * y;
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
public void Close()
{
Console.ReadKey();
}
}
public class Factoriser : IFactoriser
{
private ICalculator _calculator;
private ILogger _logger;
public Factoriser(ICalculator calculator, ILogger logger)
{
_calculator = calculator;
_logger = logger;
}
public void DoFactorTen(int value)
{
int result = _calculator.Multiply(value, 10);
_logger.Log($"The result is {result}");
_logger.Close();
}
}
Note that the Factoriser does not need to know the details about calculations and logging. Therefore these responsibilities are injected in the Factoriser through constructor injection. We are injecting the responsibilities, not the values like classSpecificInt = 10 in your example. The implementations should be flexible enough to deal with all possible values.
Now we can write the Main method like this:
static void Main(string[] args)
{
var calculator = new Calculator();
var logger = new ConsoleLogger();
var factoriser = new Factoriser(calculator, logger);
factoriser.DoFactorTen(15);
}
You could easily write this result to a file by providing a file logger instead of a console logger. You could inject the file name into the logger through the constructor. In this case it makes sense to inject a value, because the logger will have to log into the same file during its whole lifetime.
This would not have an impact on the Factoriser, since an abstract ILogger is injected.
This approach implements these SOLID principles:
- Single-responsibility principle (SRP).
- Open–closed principle:
- We can extend the behavior of our interfaces and implementations by deriving new interfaces and classes from them, i.e., without modifying the existing ones.
- Liskov substitution principle (LSP):
- We can inject a class derived from our calculator or logger or inject completely different implementations to the Factoriser without the Factoriser knowing it and without breaking the program.
- The Interface segregation principle (ISP):
- Our interfaces declare a minimal API and thus our implementations do not depend on methods they do not use.
- The Dependency inversion principle (DI):
- Factoriser depends upon abstractions (i.e. interfaces), not concretions (i.e., not specific classes). Your implementation of
Factoriserdepends on concrete implementations because it calls, e.g.:new SingleResponsabiltyApproach1(..).
- Factoriser depends upon abstractions (i.e. interfaces), not concretions (i.e., not specific classes). Your implementation of
Note also that IFactoriser does not depend on the other interfaces. This gives us a high degree of flexibility in implementation.
The Single Responsibility Principle (SRP) basically says:
A Class/method must have only one responsibility
So, to go over this principle, think about a class Car with a god method called TurnOn(). Inside this method, you start the car, turn the lights on, accelerate, and brake. And another method called TurnOff(), turning off engine, lights, and braking.
Car
- TurnOn()
- TurnOff()
If you need to use this class, you may think the method TurnOn() only turns the car on, which is not valid, and it breaks the SRP. The same applies for the TurnOff()
Applying the SRP, the class Car must have the methods:
Car
- TurnEngineOn()
- TurnEngineOff()
- Accelerate()
- Brake()
- TurnLightsOn()
- TurnLightsOff()
So now, if you need to use the Car class, you know exactly how to use each part of the car independently.
You can notice every method with a specific responsibility.
I changed a few things in your example to apply the SRP:
namespace SingleResponsibility
{
internal class Program
{
// All the UI interaction (read, write) happens in the main method (UI layer with the user)
// So the single responsibility of the UI is only calling methods and interacting with users
static void Main(string[] args)
{
Console.Write("Tell me a number to factorise: ");
int myNumber = Convert.ToInt32( Console.ReadLine() );
SingleResponsabiltyApproach1 sra1 = new SingleResponsabiltyApproach1(myNumber);
Console.WriteLine( $"My first approach {sra1.DoFactorTen()}" );
SingleResponsabiltyApproach2 sra2 = new SingleResponsabiltyApproach2();
Console.WriteLine($"My second approach {sra2.DoFactorTen(myNumber)}");
Console.ReadLine();
}
}
// The single responsibility of this class is to do an approach using a parametered constructor
internal class SingleResponsabiltyApproach1
{
// using property as private (encapsulated)
private int PassedInt { get; set; }
// starting the constructor with a parameter
public SingleResponsabiltyApproach1(int passedInt)
{
this.PassedInt = passedInt;
}
// doing the factor
// The single responsibility of this method is to do a factor ten, and its name really means this
public int DoFactorTen()
{
return PassedInt * 10;
}
}
// The single responsibility of this class is to do an approach using a default constructor
internal class SingleResponsabiltyApproach2
{
// no custom constructor
// doing the factor passing number as parameter
// The single responsibility of this method is to do a factor ten with a number
// provided, and its name and signature really means this
public int DoFactorTen(int passedInt)
{
return passedInt * 10;
}
}
}
If you want to go over interfaces and dependency injections, maybe you can go over the other more complex principles, such as Liskov Substitution Principle (LSK), Interface Segregation Principle (ISP), Dependency Inversion Principle (DIP).
Cheers!