The Single Responsibility Principle states that:
A class should have one, and only one, reason to change.
The Open/Closed Principle states that:
You should be able to extend a classes behavior, without modifying it.
How can a developer respect both principles if a class should have only one reason to change, but should not be modified?
Example
The factory pattern is a good example here of something that has a single responsibility, but could violate the open/closed principle:
public abstract class Product
{
}
public class FooProduct : Product
{
}
public class BarProduct : Product
{
}
public class ProductFactory
{
public Product GetProduct(string type)
{
switch(type)
{
case "foo":
return new FooProduct();
case "bar":
return new BarProduct();
default:
throw new ArgumentException(...);
}
}
}
What happens when I need to add ZenProduct
to the factory at a later stage?
This feels like a discussion of the semantics of 'extend a classes behaviour'. Adding the new type to the factory is modifying existing behaviour, it's not extending behaviour, because we haven't changed the one thing the factory does. We may need to extend the factory but we have not extended it's behaviour. Extending behaviour means introducing new behaviour and would be more along the lines of an event each time an instance of a type is created or authorising the caller of the factory - both these examples extend (introduce new) behaviour.
A class should have one, and only one, reason to change.
The example in the question is a factory for creating Product
instances and the only valid reason for it to change is to change something about the Product
instances it creates, such as adding a new ZenProduct
.
You should be able to extend a classes behavior, without modifying it.
A really simple way to achieve this is through the use of a Decorator
The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
public interface IProductFactory
{
Product GetProduct(string type);
}
public class ProductFactory : IProductFactory
{
public Product GetProduct(string type)
{
\\ find and return the type
}
}
public class ProductFactoryAuth : IProductFactory
{
IProductFactory decorated;
public ProductFactoryAuth(IProductFactory decorated)
{
this.decorated = decorated;
}
public Product GetProduct(string type)
{
\\ authenticate the caller
return this.decorated.GetProduct(type);
}
}
The decorator pattern is a powerful pattern when applying the SOLID principles. In the above example we've added authentication to the ProductFactory
without changing the ProductFactory
.
A class should have one, and only one, reason to change.
This basically means, your class should represent single responsibility and shouldn't be modified thereafter to accommodate new feature.
For example, if you have class, which is responsible to print report in pdf format. Later, you wanted to add new feature to support printing report in other formats. Then instead of modify the existing code, you should extend it to support other format, which also implies extend a classes behavior, without modifying it
I think it depends on your interpretation of the SRP. This stuff is always somewhat subjective. Ask 100 people to define "single responsibility" and you'll probably get 100 different answers.
Using the scenario in Ravi's answer, a typical solution might be to have a ReportGenerator
class which exposes a GeneratePdf
method. It could then be later extended with an additional GenerateWord
method if required. Like yourself though, I think this has a whiff about it.
I would probably refactor the GeneratePdf
method into a PdfReportGenerator
class and then expose that through the ReportGenerator
. That way the ReportGenerator
only has a single responsibility; which is to expose the various report generation mechanisms (but not contain their logic). It could then be extended without expanding upon that responsibility.
I'd say that if you find a conflict, it might well be an architectural smell that warrants a quick review to see if it can be done in a better way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With