June 05, 2006
The Single Responsibility Principle
In a previous post I discussed how the Open Close Principle (OCP) is one of the most basic principles of OO design. This post is focuses on the Single Responsibility Principle (SRP), also known as "high cohesion" which is another fundamental design principles.
The Encarta Dictionary defines cohesion as:
Sticking or working together: the state or condition of joining or working together to form a united whole, or the tendency to do this.
The same should be true for classes. A class should be cohesive; that is, have only a single purpose to live and all its methods should work together to help achieve this goal. Or in Robert C. Martin's words:
There should never be more than on reasons for a class to change.
When a class has more than one responsibility (that is, reason to change) these responsibilities are coupled. This makes the class more difficult to understand, more difficult to change, and more difficult to reuse (rigidity, immobility and needless complexity). Cohesion should also be applied at the method level--and for the exact same reasons.
You can also take a look at David Chelimsky's blog where he talks about not fragmenting that single responsibility between classes.
The challenge with SRP (and with David's extension of it) is getting the granularity of a responsibility right. For example, if I have a class that represents a business entity, should it also save itself to the database? Is handling all aspects of the customer including its persistency a single responsibility or is handling its business aspects one and persisting the second? In "Patterns of Enterprise Application Architecture" Martin Fowler et al. present the "ActiveRecord" pattern where a class wraps a database row (the first option described above). On the other hand, Alistair Cockburrn describes the Hexagonal architecture which talks about separating domain from transformation--persisting to a database (the second approach described above). I'll discuss additional aspects of this particular issue shortly when I examine architectural dilemmas.
Sometimes it is easier to see the responsibilities are unrelated. For instance, consider the same customer entity mentioned above. Should it also contain the logic needed to render it on the GUI? Here it is easier to see that the GUI representation should be handled in a different class. GUIs change a lot (relatively), and the entity may have several views on the UI (a grid, a specific form etc.). The entity and its representation may need to run on completely different tiers etc.
Sometimes the second responsibility may seem so trivial you may be tempted to leave it anyway. For example, creating a (threadsafe(!)) singleton in C# (if you don't want lazy initialization) is as simple as:
sealed class Singleton
{
private Singleton() {}
public static readonly Singleton Instance = new Singleton();
// other business related code goes here
}
Why bother separating the responsibility to have a single instance (singleton) from the business responsibility?
The reason is the coupling this induces on the consumers of the class which is very hard to remove. I recently had to do just that. I worked with a group that had a few singletons and code with dependencies on them. It was basically some application-level protocol layer (for communicating with external devices) the idea was to reuse it for a sister project that had to talk with the same devices. They said they had some problems reusing the code and asked me to take a look to see if I can help. I thought that since we're talking about the same devices, plus some of the code is generated it would be a breeze, maybe we'll do some refactoring and everything will be okay. It turned out we had to do a major redesign to uproot all the application specific singletons in to make the core code more mobile. You can read more on why singletons are bad on the C2.com Wiki and on Scott Densmore's blog.
One last point regarding SRP: If you cannot separate the responsibilities into separate classes, at least consider separating them to different interfaces. I will elaborate on this when I'll write on the Interface Segregation Principle.
Posted by Arnon Rotem-Gal-Oz at 04:58 AM Permalink
|