From a long time ago I’ve been reading a lot of post about technologies, good practices, processes and tools. After reading this great POST, I’ve decided to start writing some articles to review and strengthen some concepts. So today let’s take a quick look at my first post about SOLID principles.
As probably you already know, SOLID is an acronym introduced by Michael Feathers and it is based on Uncle Bob’s paper which defines five basic object-oriented design principles. These principles are rules that help developers to mitigate the design smells and bad design. The goal behind these principles is the foundation for building code that is maintainable, flexible, scalable and reusable. Like design patterns, SOLID allows us to create code with high cohesion and low coupling. It doesn’t matter that you’ve just started writing software or if you have been doing so for years. If you start following these principles while coding, it will be appreciated by every individual contributor.
So, let’s go one by one in swift way …
The Single-Responsibility Principle (SRP)
A class or module should have one, and only one, reason to be changed.
The book definition says that a class should have one, and only one, responsibility. In this context, Martin considers responsibility as a reason to change. If you can think about more than one motive for changing a class, then that class has more than one responsibility.
So, every class should be responsible for doing exactly one thing and doing it well. If you change anything in that class, it will affect only one particular behavior of the software. This makes the code more robust because there will be less side effects. If a class had two or more responsibilities and you changed something, the risk of breaking the logic of the other behavior would be high, so it would lead to fragile code, taking this into account, it is difficult to refactor when new requirements emerge. This principle leads to a stronger cohesion in the class and looser coupling between dependency classes, a better readability and a code with a lower complexity. In the same way, this principle applies to methods, packages and libraries.
Let’s consider a piece of code that violates the principle:
The code above violates the SRP, the UserSettings class has two responsibilities: to verify the user credentials and to make some settings. Here you can check another example.
SRP is one of the easiest principles to learn, but at the same time the hardest to master. A good way to apply this principle indirectly is to use TDD as a development technique.
The Open/Closed Principle (OCP)
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
OCP states that a module should be open for extension but closed for modification. Which means that you can add new features but you should not change the existing code. The reason for this is that if you modify a class again and again, you’ll likely break the contract of the class which means that the classes that depend on it might fail when you do so. Also, you need to ensure that the previous code with new ones are tested again, and that the existing referencing object of this class works properly as before, etc.
Abstractions are the mechanism to provide OCP. The behavior specified in client can be extended by creating new implementations for the interface. But, If you inherit the class to add new features, the base contract is untouched and it’s unlikely that dependent classes fail.
Suppose we need to model a new format file (e.g JPEG), we need to add a new if statement and broken OCP. So, the best way should be by using a File interface with a print method, an each format type inherits and implements the interface. In the printFile, replace the if statement for just a print call.
As you know, we can’t predict all the changes that a software will require. So, here are some points to considerate:
● Provide abstractions that make the system testeable.
● Beware of needles complexity. We do not want to load the design with lots of unnecessary abstractions.
● Wait until we need the abstraction and put it there.
The Liskov Substitution Principle (LSP)
Child classes should never break the parent class’ type definitions.
As its name says, LSP was defined by Barbara Liskov. The idea here is that objects should be replaceable by instances of their subtypes, and that without affecting the functioning of your system from a client’s point of view. Basically, instead of using the actual implementation, you should always be able to use a base class and get the result you are waiting for.
This principle confirms that our abstractions are correct and helps us to get a code that is easily reusable and class hierarchies that are very easily understood. LSP is close to OCP, a violation of LSP is a latent violation of OCP.
HERE you can find a nice code example
The Interface Segregation Principle (ISP)
Many client-specific interfaces are better than one general purpose interface. Avoid ‘fat’ interfaces.
This principle indicates that clients should not be forced to depend on methods they do not use. So, classes should be as specialized as possible. You do not want any god classes that contain the whole application logic, avoid ‘fat’ interfaces. The source code should be modular and every class should contain only the minimum necessary logic to achieve the desired behavior. When you have clients who share a group of services you can create a new interface for the common methods or create a hierarchy of interfaces. A nice option is Facade and Adapter patterns to segregate interfaces.
As you can see above, the Car interface expose methods that aren’t common to all users. So, we need to add an intermediate interface for DeloRean car.
The Dependency-Inversion Principle (DIP)
High/low level modules and details should depend on abstractions.
DIP suggests that classes should not depend on concrete details of other classes. Even classes from a high domain level should not handle the specific details of components from a lower level. Both low and high level classes should depend on the same abstractions. So, you should let the caller create the dependencies instead of letting the class itself create the dependencies. Hence inverting the dependency control (from letting the class control them to letting the caller control them).
Coffee class has to know Milk class in order to calculate the cost of coffee, so we are much more attached to that specific class. So, consider use abstractions.
HERE you can find other example.
So, The SOLID principles are guidelines and as Uncle Bob says: “They are not laws. They are not perfect truths”. As a developer, I think you should at least know them so you can decide when to apply them and when not to.
Keep in touch
If you really enjoyed this writing, please feel free to share it so others can find it too. If you have any questions, concerns or if you simply want to add your thoughts to this conversation, you are kindly welcome to do that in the comments box. You also can reach me at @fgriberi. Thanks a lot 😊