Real Swiss don’t need SRP, do they?
You may ask yourself why I publish another article about the single responsibility principle (SRP). We already had some very good post about this principle at Los Techies, e.g. here, here and here to mention just a few. Well, the reason is that I consider this principle one of the most helpful ones to achieve higher quality of code and better design when applied consistently. And I want to approach this topic from a different standpoint than is usually done by other authors…
What does SRP mean? The theoretical explanation (you might have read or hear many times already) is
“There is one and only one reason to change a class.”
What does this mean? This means that we should start to think small. Each complex problem cannot or cannot easily be solved as a whole. It is much easier to first divide the problem in smaller sub-problems. Each sub-problem has a reduced complexity compared to the overall problem and can then be tackled separately. There is a proverb whose origin is from the Romans which says: “Divide et imperat”. Translated to English this means: “Divide and reign”. It was not possible to the Roman emperor to reign the whole empire alone. No, he divided the empire into independent regions and appointed a king or sovereign to each region. At the end the Roman emperor had not much more to do than orchestrate those kings or sovereigns that reported to him.
Now what does this mean for me as a developer? Each developer has to solve problems. Very often these problems are rather complex. Often the boundary conditions defining the problem even change. Now we should start thinking in terms of “divide et imperat”. Let’s find the sub-problems in the domain we are working in. Keep on dividing each sub-problem into sub-sub-problems until you reach the point where such a “mini-problem” has just one single task left. Let’s then solve each of those “mini-problem” in it’s own class. Since each class only has one single task to fulfill there is (as a consequence) only one reason left to change this class. We only have to change this class if the corresponding task changes.
Instead of tasks one often talks of responsibility. Each class should have a single responsibility. The responsibility is to just accomplish the assigned task.
I want to say it again: Applying the single responsibility principle leads to higher quality of code and to better design! Why? Because the code is
- more readable, that is easier to understand
- less error prone
- more robust
- better testable
- better maintainable and extendable
Swiss people think differently…
But wait a moment. Swiss people are not descendants of the Romans…
A real Swiss does not need to follow the single responsibility principle. That’s something for others but not for us. As a representative sample I want to present you one of our most successful products: the Swiss army knife.
One can clearly see that this product has not just one single responsibility. There are several responsibilities assembled in a single unit. We have 2 knifes, one can opener, one bottle opener, an awl and a corkscrew. This unit is very handy and fits well into the pocket of every real Swiss man.
Some people prefer to even pack more functionality into this unit as you can see in this second picture at right.
Another tool comes to my mind when remembering the time I passed in the Swiss army. Our helmet is also considered to be a multi purpose tool. We primarily use it to protect our heads from injuries but it has as well served me many times as a pillow. It is even considered as an anti-hand-grenade tool. We were told that if a hand grenade is thrown at us and we have no time or possibility to throw it away then we should just put our helmet over it and burden it with our body. To be honest, I’ve never tried it…
Hey, wait a moment. I can give you another sample where we clearly show to the rest of the world that the SRP is not for us. It’s our famous Swiss cows. They are not only good for providing us milk and eventually meet; no, they are also very good soccer players! Currently we have 3 of them in our national soccer team.
Not to forget the famous Milka cow! Here a cow is used as an advertising medium. This is a very important responsibility by its own.
Well, I could possibly continue to give you good samples of Swiss products that are really successful without respecting the SRP.
Why does the rest of the world consider SRP to be important?
Imagine one of the items of a Swiss army knife gets bent. It would possibly render the whole item useless or at least harm its functionality. I will have to throw away the whole knife. What a waste!
Or imagine that I am really happy with my Swiss army knife but just one element does not totally fit my needs. I would like to replace just this element with another one which is better suited to my needs. I cannot do it! It’s just not possible without (negatively) affecting all the other elements of the knife.
Imagine having a nice dinner with your wife or husband in a first class restaurant. You certainly have had lots of knifes, forks and spoons. Each element serving for a single purpose. There are knifes to cut steaks or pizzas or knifes to eat fish and so on. Each item is optimized for it’s specific task. If one of these items gets broken or if it doesn’t fulfill it’s duty any more then it can be replaced without affecting the other items.
The same can be said for the glasses and dishes. It doesn’t make sense to have only one single glass for all kinds of beverages. If you like wine then you know what I mean. Red wine tastes significantly better in bigger glasses than white wine.
The same can be said about the dishes. It just doesn’t make sense to serve soup in the same dish as a nice T-bone steak with baked potato is served.
Let’s start coding!
Too many developers still don’t respect the SRP. I consider this as one of the primary reasons why an application get’s unmanageable with the time. More and more the code base resembles a plate of Spaghetti. Consider the following sample.
A sample XML document could be
Such kind of code we can find all the time in any type of company. Such code is not only produced by part time developers but also by a lot of developers considering themselves as being professional developers. I would consider this not to be an exception but rather the norm.
The story behind this code is:
The user can select an XML document which contains a list of products from the file system. This XML document is then loaded and display it on screen.
I have kept the above code sample as short as possible. The structure of the XML is very simple, the product has only very few attributes and there is no error handling. In reality the above code would be much longer and convoluted.
Now let’s analyze which and how many responsibilities the above code has:
Refactoring step by step
Step 1: Defining a model
One of the first concepts one can find is an implicit model. Since we are importing product data from an XML document it makes sense to introduce a Product entity as our model
By carefully analyzing the above code snippet and the given XML document we can define the following model
Step 2: Extracting the loading (and parsing) into a repository
We also can recognize a distinct concern of loading a list of products from a data source. In this case the data source is a XML document
Wouldn’t it make sense to have a specific component whose concern it is to load a list of products from a data source and just return a list of instances of type Product? Some thing like this:
The repository is responsible to load, parse and map the XML document and return just a list of Product items to the caller.
If we now refactor the sample with the assumption of having a product entity and a product repository the code might look like this:
Step 3: Introducing a presenter and extracting logic not related to presentation from the view
Although our code already looks much more polished than before we can still not be happy. A view (and here the form is a view) should only contain logic that is strictly related to presenting data and delegating requests triggered by the user to a controller or presenter. Thus we introduce a pattern which separates the concerns of a) visualization, b) orchestration and c) (data-) model. A pattern that perfectly fits our needs is the Model-View-Presenter pattern (MVP). The presenter is the component that orchestrates the interactions between model, view and (external) services. In this pattern the presenter is in command.
Let’s analyze what should be the responsibility of the view
- delegate the user’s request to choose an XML document to the presenter
- delegate the user’s request to load the data from the selected XML document to the presenter
- provide the name of the selected XML document to the presenter
- accept a file name (of a selected XML document) from the presenter
- display a given list of products provided by the presenter (in a ListView control)
Assuming that we have such a ProductPresenter class we can then refactor the view (or form-) code like this
Note that the above code now contains only display related code or code that delegates a user request to the presenter. We have reached good separation of concerns so far.
Now we have to implement the presenter. As said before the presenter is responsible to orchestrate the collaboration of model, view and external/additional services. Thus the presenter should not contain any business logic. The presenter should be slim and slick! He delegates all work to other components.
Avoid implementing a fat presenter which is considered to be an anti-pattern.
We have identified so far (see code above) that the presenter needs at least the following two methods
and the presenter accesses it’s view (that is in this case the form) via an interface
which has to be implemented by the form. That is
Let’s have a look at the implementation of the presenter
It’s obvious from the above code that the presenter does not do much more than orchestrate. It interacts with the view and the repository as well as with an open file dialog service (this is just a wrapper around the OpenFileDialog class of the .NET framework).
Please note the second line in the constructor. The presenter calls the Initialize() method of the view and passes itself as a reference. As such the view gets knowledge of it’s responsible presenter. Remember that the presenter is in command in the model-view-presenter triad!
Starting the application
How can the application be started? After the refactoring we do not give the command to the view/form but to the presenter. Thus we might have something like this:
In the Main() method we instantiate a product presenter which in turn internally creates an instance of it’s dedicated view (which is a form in our case). We then use the presenter’s view and pass it to the Run() method of the application object.
We have to add a property View to the presenter to complete it’s implementation
Step 4: Implementing the repositiory
We still have to implement the repository. There is just one method we need to implement
Now I have every piece needed to make the application run.
But wait a moment! There are still at least 3 concerns handled by the repository class. One is the retrieval of the data, another one the looping over the nodes of the XML document and the third one is the mapping of a XML node to a product. So let’s refactor again:
Now I have a file loader which is responsible for loading the XML document and returning it as a stream to me. And I also have a mapper which is responsible to map a single XML node to a product. As a result the code of the repository has become very simple and manageable.
So let’s have a look at the implementation of the mapper component
The mapper code is very straight forward. I have even introduced some basic error handling. Note that I could still go farther with SRP and introduce an XML attribute parser (helper-) class if I want to go to the max… but let’s just stop here for the moment!
The implementation of the (file-) loader is also very simple
Class diagram of the fully refactored sample
The image below shows the class diagram of the fully refactored sample. There are many components involved in this little sample. But each component is very simple and has just one single responsibility.
The sample code
You can find the code of the original and the fully refactored sample here. Just use a SVN client like TortoiseSVN to download the code.
You might be overwhelmed by the sheer amount of classes (and code) introduced by the refactoring. For this simple sample it is certainly an overhead not worth the investment. But don’t forget that real applications are way more complex than this simple example. The more complex and the bigger an application becomes the more important is SRP. With the aid of the SRP a complex problem can the be reduced to many small sub-problems which are easy to solve in isolation. Just remember the sample of the Roman empire I gave you in the introduction of this post.
In my daily work, when respecting and applying the SRP I always got huge benefits. My code is more robust, more stable, better understandable and maintainable. At the end I get a better and much cleaner design.