S.O.L.I.D. principles, why are they so important?

S.O.L.I.D. principles, why are they so important?

We hear a lot about these principles, they are also a very frequent interview question and there's a good reason why people should be aware of them.

These are principles considered for object-oriented programming languages, especially because these are referring to good practices for design, encapsulation and security.

Let's go through a quick overview of each principle:

S - Single responsibility principle

A class must have a single reason to change, meaning it must have a single job. This is probably one of the most obvious principle when we talk about object-oriented programming, considering it's all about encapsulation and having different classes with their own purpose. Anyway, in my experience, I was involved in a lot of projects where people don't encapsulate correctly, classes contain a huge amount of lines of code, and we clearly can see there's not only one single job in these classes.

Taking this principle in consideration is the most effective way to remember how small your class must be.

O - Open-closed principle

Our implementation or product must be open for extensions but closed for modification. Think about third-party libraries, generally you can extend their functionality, either extending an existing class or implementing one of their interface in order to use it later through some provided configuration. But you can never modify their source code, that won't be available considering most of the third-party libraries are distributed through a binary file that was already compiled previously, and there are other techniques to prevent modifications, for example using some sealed classes to prevent extensions if they want to extra security layer, or even if you are using the source code for a third party library, you can find some internal classes that are available within the assembly used by the library and you won't be able to use them as long as your code is compiled in a different assembly.

L - Liskov substitution principle

The principle states that, if B is a subtype of A, then an object of type B may be replaced by an object of type A.

This is easier to understand when we think about inheritance, the hole purpose of inheritance is that we have a base class where we can extend its functionality through sub-classes, providing different behaviours. With this idea in mind, the principle indicates that your code should reference the base class when is intended to be used, then the behaviour can be different depending on the subclass instantiated previously, this way we can provide different behaviour and our main implementation will remain the same.

I - Interface segregation principle

We have to prevent using interfaces that we're not going to use, or are not useful for the client. This is also a idea related to single responsibility, considering we are looking for interfaces used for a single job, at the same time every method described in the interface must have a meaning for classes implementing it.

If we have an interface for data source operation with read and write methods, probably this is good interface if we consider all data source support read and write operations. Anyway if we consider read-only data sources, we would prefer to segregate our interface and split it in two different interfaces, one for reading and another one for writing (or probably writing is not allowed or is used internally by the implementation only and it's not required in the interface)

D - Dependency inversion principle

Your class must depend on abstract implementations instead of concrete implementations, meaning that high level implementations must not depend on low level implementations, both should depend on abstractions.

If you are familiar with dependency injection, this is a design pattern that takes this principle in consideration. Basically, it injects class dependencies through the constructor, and the class will be expecting an abstract type where behavior can change, but we are aware of the implemented methods that we have available.

Here I provide an example to clarify a wrong case scenario, and how to fix it:

interface A {
    void doSomething();
}

class B implements A {
    public void doSomething() { print('doing something in B'); }
}

class C implements A {
    public void doSomething() { print('doing something in C'); }
}

// wrong!
class Main {
    public Main(B b) {
        // we are using a concrete implementation, and we are not able to inject a different implementation like C
        b.doSomething();
    }
}

//right way to inject dependencies
class Main {
    public Main(A a) {
        // now we use an abstraction (the interface), and we can inject an instance of either B or C
        a.doSomething();
    }
}

Conclusion

For those who never heard about SOLID principles but still have years working with OOP programming languages, probably they were applying most of these principles already, using good practices that include encapsulation, well-implemented design through clear interfaces, or maybe a dependency injector that benefits unit tests. Anyway, it's good to have these principles in mind and remember how well they describe the meaning of using OOP and how this is going to create a good structure and security layer for your projects, at the same time you will get a lot of benefits for team-based projects.


Share Tweet Send
0 Comments
Loading...