Barely a day goes by without someone mentioning SOLID principles these days.
“Hello colleagues,” I say, “I am going to the coffee machine to get some coffee. Would you also like some coffee?”
“That depends,” reply my colleagues, “on whether the coffee will made according to SOLID principles.”
I consider this for a moment before replying. “Don’t be ridiculous. It’s a cup of coffee not a software engineering project.”
And there the matter ends.
As far as acronyms go, SOLID is towards the SPLINK end of the convolution spectrum. In this article I’m going to go through each of the five principles in a way that will hopefully make them more memorable.
S is for Single Responsibility
This one’s pretty straightforward, or at least initially appears to be so. A class should have a single responsibility, or in layman’s terms, should have only one job to do. But wait! Slavishly following the principle as described would lead to lots of classes with single methods inside, which will make your code somewhat obscure. There’s a qualifier to the Single Responsibility Principle, and it is that a class should have only a single reason to change. This is perhaps a little harder to understand, so let’s have an example.
Imagine we have a class called Squirrel. This contains two methods:
- CountNuts() – Returns the number of nuts the squirrel currently has.
- ClimbTree(Nuts nuts) – Checks the number of nuts with a call to CountNuts() and only climbs the tree if it is over a given threshold. Climbing trees requires squirrel fuel.
This violates the Single Responsibility Principle because there are two reasons for the Squirrel class to change:
- The way that nuts are counted might change, eg different nuts may be given different weightings
- The nut threshold to climb a tree might change
To fix this, as a bare minimum the ClimbTree(Nuts nuts) method should be moved to its own class, and given an additional Squirrel parameter so that the Squirrel object can be passed into it: ClimbTree(Squirrel squirrel, Nuts nuts).
O is for Open / Closed
Which is it, open or closed? Are we talking about a programming principle or a CD drawer here? This is definitely one of the more obscurely named parts of the acronym. It may as well be called Owls.
Owls, being very wise birds, are open to the idea of being extended but closed to modification. Ask a barn owl to change into a tawny owl it will quite rightly tell you to sling your hoo-k. However ask it nicely to hoot like a tawny owl and it will happily oblige.
Let’s say we have an Owl class containing a single method: Hoot(OwlType owlType)
This method takes the type of owl (barn or tawny) and returns the appropriate hoot. Logic within the Hoot method constructs the hoot depending on which type of owl is supplied.
There is a problem with this. If we want our owl to be able to hoot like a snowy owl, we will have to modify the Owl class. This breaks the Open / Close principle because our class should be closed to modifications. Instead, it should be open to being extended. In this case we should do this by changing our Hoot method to accept an interface instead of an owl type: Hoot(IOwl owl).
The interface IOwl has a method called Hoot, and it is this which is called by the Hoot(IOwl owl) method. We then have concrete implementations of IOwl for barn and tawny owls, each of which have their own implementation of Hoot. With this structure in place, adding the ability to hoot like a snowy owl is simply a matter of creating a new TawnyOwl class which inherits IOwl.
L is for Liskov Substitution
Professor Barbara Liskov is one of the first women in the US to receive a doctorate in computer science. She has been doing this shit since the ’60s and has probably forgotten more than I will ever know. Take a look at her website. Gosh, it looks like the 1990s doesn’t it? Don’t laugh, she has more important things to do than worry about that grey background. Look closer. Look at her CV. It’s 32 pages long but 30 of those pages are publications and academic contributions to computer science. It’s fair to say that she’s pretty awesome.
That’s all great, but L for Liskov doesn’t tell us much about what Liskov Substitution actually is. In Prof. Liskov’s paper with c-author Jeannette Wing summarises it as:
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
It’s not exactly plain English but this is from an academic paper. Give your head a good scratch and you can make sense of it. In the world of C# Liskov Substitution can be described more plainly like this:
A class derived from a base class, interface or similar structure must be interchangeable with its base class or interface, etc.
Let’s say you have some Llamas. Everyone knows that Llamas love Libraries. They are nature’s most avid readers, with a strong penchant for Literature. Unfortunately being quite Large creatures there are only so many llamas that can fit inside a library at once. We can call our collection of Llamas from the Library class and get it return its count to control the number of llamas per library.
But wait – some alpacas want to join the library. They are nature’s second most avid reader and in many other ways are similar to llamas. In fact the only way in which they differ that matters to us is that they’re about half the size of a llama. This means that more alpacas can fit into a single library.
It’s tempting to simply create a new Alpaca class with derived from the existing Llama class, and give it a Size property. This is set to 0.5. However doing so would break the Liskov Substitution Principle. Although our Library class can access the Alpaca’s Size, it cannot do the same for a Llama because the Llama class doesn’t have a Size property. The base class (Llama) cannot be substituted for its derived class (Alpaca).
One way to fix this is to add Size to the Llama class and override it in Alpaca. However this may not be the ideal solution, particularly if the Library one day decides to admit Sheep, nature’s third most avid readers. They could have all sorts of things which are different to Llamas. In that case it makes more sense to make a new base class or interface which includes the Size property, and derive Llama, Alpaca and Sheep from that.
I is for Interface Segregation
This one is more straightforward and at its heart is the idea that interfaces should contain the minimum required members. That way anything using that interface doesn’t need to concern itself with members it doesn’t use.
Let’s say with a class called Insect. This contains many members such as NumberOfWings and StingStrength. If we want to Sting() another object using our Insect class, we could just pass the whole Insect in as a parameter, like this: Sting(Insect insect). However, doing so exposes all the other members of the Insect class to the Sting method, when all we need to know about is StingStrength. We can reduce this exposure by creating an IStingable interface with a single member, StingStrength, and implementing it in the Insect class. We can then pass this into Sting like so: Sting(IStingable insect)
Similarly, if we have a Fly(Insect insect) method, we can create an IFlyable interface which contains the single member NumberOfWings, and use this like so: Fly(IFlyable insect)
Now we have two very tight interfaces for our Insect class which segregate its behaviour so that client code is only concerned with the parts it needs access to.
D is for Dependency Inversion
A common mistake is to construct high-level classes using concrete classes from further down the class hierarchy. What does this mean? Well, let’s look at everyone’s favourite seaborne scamp, the dolphin.
We have a higher-level class called Tricks which at the moment contains a single method, DoTrick(Dolphin dolphin). This will work, but becomes problematic if we want another animal to do a trick. As written, the high-level class is dependent on a low-level class. We need to redesign Tricks so that both it and the low-level class instead depend on an interface, ITrickable.
Now the DoTrick method is defined as DoTrick(ITrickable trick) and the Dolphin class inherits from ITrickable. Instead of the high-level class depending on a low-level class, both classes now depend on an interface. This is dependency inversion.
In conclusion then,
I hope you’ve enjoyed this somewhat frivolous look at SOLID Principles. For a more in-depth look at the subject, I recommend these sites: