Examining the S & O of SOLID
By Caroline on June 10, 2019
This post is about the first two principles of the SOLID acronym. So what is SOLID? The five principles were first conceptualized by Robert C. Martin in 2000. Later on Michael Feathers defined these concepts into an easy to remember acronym - SOLID:
Single Responsibility
Open/Closed
Liskov Substitution
Interface Segregation
Dependency Inversion
1 | Single Responsibility
Robert C. Martin explains the principle as, “a class should have only one reason to change”. This means that objects should only have one purpose. If an object is performing many different tasks, then at that point it is has more than one responsibility; it’s purpose is not coherent. Object’s should be concerned with focusing on doing one job, and doing that job well. The concept of cohesion is a major component of single responsibilty, it was coined by Tom Demarco, and is said to have influenced Martin in the creation of the SRP. When an object has a clear focused purpose it is considered highly cohesive and therefore becomes are easier to maintain.
The other main aspect of single responsibility is the idea of coupling. Essentially this can be thought of as how much does this object rely on other things to perform it’s task. When an object is loosely coupled it can be easily switched out and modified as needed. Consider a laptop computer that has a built-in keyboard and trackpad - while they are thought of as individual components of a computer, they are entirely part of the laptop as a whole. Changing them, or getting them repaired is extremely challenging, you need the exact model. They are very tightly coupled.
On the other hand a desktop computer has a separate keyboard and a separate mouse. Upgrading and switching these out is extremely easy! You can use different components - the computer doesn’t mind what type of model, it just wants them to connect with a usb. This makes them loosely coupled.
The SRP concept also builds upon the idea of encapsulation. Thinking back to the desktop computer - a desktop only has one way to connect with an external keyboard - a usb port, and the computer only wants to know what keys have been pressed. The inner physical mechanics of the keyboard is not the concern of the computer. Encapsulation is the concept that an object’s attributes and behaviour are hidden and only the specific attributes or messages that are needed to make the system work as a whole are exposed. The keyboard just sends a message about what key has been pressed.
Why is Single Responsibility important?
When software doesn’t follow single responsibility, it can lead to the ever dangerous GOD object; an object that manages too many things. Code becomes highly coupled and has low coherence, therefore it’s very difficult to maintain. Changing one thing affects much more, and methods become inextricably linked.
Here you can see an Article
class. The class is keeping track of 3 attributes - title
, author
and the text
of the article. It has the ability to replace words and see if words exist in the article’s text. You can also choose to read the article, by calling the print_text_to_console
method.
class Article {
def __init__(self, title, author, text):
self.title = title
self.author = author
self.text = text
replace_word_in_text(old_word, new_word) {
self.text = self.text.replace(old_word, new_word)
return self.text
}
is_word_in_text(word) {
return word in self.text
}
print_text_to_console() {
print(text)
}
}
It seems like everything is directly related to managing an article. But let’s say I wanted to start creating books as well, and I also want to have the ability to read them. Would the book class also have a print_text_to_console
method? And what if I wanted to be able to print the text another way? Now it becomes obvious that the Article
class is managing too much, and instead the printing behaviour should be separated out into another class as follows:
class Article {
def __init__(self, title, author, text):
self.title = title
self.author = author
self.text = text
replace_word_in_text(old_word, new_word) {
self.text = self.text.replace(old_word, new_word)
return self.text
}
is_word_in_text(word) {
return word in self.text
}
}
class Text_Printer {
print_text_to_console(text) {
print(text)
}
print_text_to_another_medium(text) {
// Send text to another location
}
}
Much better! Now I can print any text I want with the reusable Text_Printer
class!
2 | Open/Closed
This principle essential states that objects should be open for extension, but closed for modification. What does that mean? The goal is to stop code from being constantly modified which could potentially cause new bugs in an otherwise working application. Systems should be able to adapt to change without breaking. Basically you should always be able to add new code to an object, and should never have the need to change the design of existing code.
Why is Open/Closed important?
It allows you to create code that is easy to maintain and reusable. This relates back to the coupling idea - the more something relies on how another is implemented, the higher the likelihood that you will need to alter one when changing the other. The best way to implement this principle is to use abstraction and interfaces. Interfaces provide a way to duplicate and add onto code without deforming the original design, and thereby are closed for modifications.
Consider a coffee machine - it’s principal goal is to brew coffee. Let’s say I wanted a basic coffee machine, it would have a few standard methods - brewCoffee
, that might heatWater
and notify the user isCoffeeReady
. Great! But then let’s say I wanted to also have a fancier premium machine. This one freshly grinds coffee beans before brewing the coffee, so we would need to modify the the brewCoffee
method and add in a step to grindBeans
before brewing. But oh no! the basic machine would now break - it has no idea what to do with grindBeans
!
Here is where a coffee machine interface would be ideal. An interface would essentially allow for the duplication of a standard coffee machine app into different implementations. You could have a basic coffee machine version and a premium machine version where any additional functionality can be added - thereby making it open for extension!