DEV Community

loading...
Cover image for Solid. Is It Still Useful In 2021?

Solid. Is It Still Useful In 2021?

huzaifa99 profile image Huzaifa Rasheed Updated on ・5 min read

Why Even Bother?

In the software development world, there are 2 extremes.

  • People who don't follow best practices.
  • People who follow them to the extreme.

SpongBob Gif

If you are lazy like me you mostly don't follow best-practices because YAGNI(You aren't gonna need it) but if you are like me you mostly do follow best-practices like SOLID Design Principles.

Wait. Why am I on both sides? Because I follow both depending on what I am doing. If it's simple, limited/scoped, and predictable then who needs to overthink about best practices but if it's complex, may-become-complex, should be scalable and maintainable then yeah, we need best practices.

If you are building a system that would need changes in the future then you are going to be happy about how SOLID can make your life easy.

What is SOLID?

SOLID is an acronym for 5 principles

They aim to make your code manageable, maintainable, and scalable along with other benefits.

Note

They are not Rules, but best practices.

The Person Behind SOLID

This was in the year 2000. Robert C. Martin first introduced SOLID as a subset of different design principles in his paper Design Principles and Design Patterns.

Design principles and patterns are different, SOLID are principles.

So What Do The Principles Mean?

Each SOLID principle aims to achieve a certain goal by following a certain rule.

1. Single Responsibility Principle

It aims to separate behaviors or concerns. Meaning that every piece of code should have a specific purpose of existence and it should be used for that purpose only.

Example

The following function should only validate a user given their id.

function validateUser(userId){
    // will validate user with their userId
}
Enter fullscreen mode Exit fullscreen mode

For a complete reference, check out the Single Responsibility Principle in detail.

2. Open/Close Principle

The goal is to prevent those situations in which changing a piece of code from a module also requires us to update all depending modules. Basically, we don't allow new code to make changes to our old code.

We can extend code but not modify it. One real-life use case is of those softwares that have backward compatibility.

Example

A typescript example

interface PaymentMethod {
  pay() : boolean
}

class Cash implements PaymentMethod {
  public pay(){
    // handle cash pay logic here
  }
}

function makePayment(payMethod: PaymentMethod) {
  if(payMethod.pay()){
    return true;
  }
  return false;
}
Enter fullscreen mode Exit fullscreen mode

In the above code if we want to add credit card payment all we have to do is add the following code (along with the actual implementation) and it will work just fine

class CreditCard implements PaymentMethod {
  public pay(){
    // handle credit pay logic here
  }
}

Enter fullscreen mode Exit fullscreen mode

For a complete reference, check out my other article on Open/Close Principle.

3. Liskov Substitution Principle

What this principle tells us is that if we replace an instance of a child class with a parent class, our code should still work fine without breaking or having side effects.

Example

class Printer{
    function changeSettings(){
        // common functionality
    }

    function print(){
        // common functionality
    }
}

class LaserPrinter extends Printer{
    function changeSettings(){
        // ... Laser Printer specific code
    }

    function print(){
        // ... Laser Printer specific code
    }
}

class _3DPrinter extends Printer{
    function changeSettings(){
        // ... 3D printer specific code
    }

    function print(){
        // ... 3D printer specific code
    }
}
Enter fullscreen mode Exit fullscreen mode

This principle however has its limitations some of which I discussed in its own separate article. See Liskov Substitution Principle for an example of its limitations.

4. Interface Segregation Principle

This principle aim's to use role interfaces (or role modules in general) which are designed for a specific purpose and should only be used for those. It says

Clients Should Not Be Forced To Depend Upon Interfaces That They Do Not Use.

This principle solves some of the issues with the Interface Segregation Principle like the Bird example I mentioned in my article on Liskov Substitution Principle

Example

This is a typescript example but still not too difficult to understand.

interface BirdFly{
    fly(): void;
}

interface BirdWalk{
    walk(): void;
}

class Duck implement BirdFly, BirdWalk{
    fly(){
        // Duck can fly
    }   
    walk(){
        // Duck can walk
    }
}

class Ostrich implement BirdWalk{
    walk(){
        // Ostrich can walk
    }
} 
Enter fullscreen mode Exit fullscreen mode

For a complete reference, check out the Interface Segregation Principle in detail.

5. Dependency Inversion Principle

It focuses on using abstraction or facade/wrapper patterns to hide details of low-level modules from their high-level implementation.

We basically create wrapper classes that sit in between high-level and low-level modules. This helps a lot if the low-level implementations are different from each other.

Example

Again a typescript example

interface Payment {
    pay(): boolean
}

// (Wrapper/Abstraction around cash payment)
class CashHandler implements Payment {
    constructor(user){
        this.user = user
        this.CashPayment = new CashPayment();
    }

    pay(amount){
        this.CashPayment.pay(amount)
    }
}

// (low-level module)
class CashPayment {
    public pay(amount){
        // handle cash payment logic
    }
}

// (High-level Module)
function makePayment(amount: number, paymentMethod: Payment){
    if(paymentMethod.pay(amount)){
        return true;
    }
    return false;
}
Enter fullscreen mode Exit fullscreen mode

For a complete reference, check out the Dependency Inversion Principle in detail.

When To Use What And Avoid What

Now that we know a brief about each principle, we will look at when to use and avoid them.

Use Avoid
Single Responsibility For Scalable and Maintainable Code. When too much Fragmentation occurs without predictable future changes.
Open Close To prevent old code from breaking due to a new one. When over-engineering.
Liskov Substitution Parent/Child used interchangeably without breaking. When substitutions do not make sense. (Bird example)
Interface Segregation For Role-specific interfaces. When difficult to aggregate (due to a lot of modules) from segregation.
Dependency Inversion For different low-level implementations. When different implementations of a low-level module are not needed, like the String class in most languages are not changed because it's not needed to mostly.

These are mostly the reasons and you can disagree but it all comes down to what you are dealing with.

Is SOLID Still Useful in 2021?

Ask yourself. Is there a language that does everything with one line of code?

do_everything();
Enter fullscreen mode Exit fullscreen mode

I guess not, unless you or someone makes a language that uses less code than python and does everything with one line of code, you do need SOLID design principles.

Of course, there are extremes and cases where it's just not possible to implement SOLID, but if you are comfortable and can use SOLID then you probably should.


Conclusion

So, what's your take on this? Do you follow an approach similar to mine? Be sure to give this article a 💖 if you like it.

Discussion (17)

pic
Editor guide
Collapse
leob profile image
leob • Edited

My opinion is that these 5 principles are not all on the same "level". Some of them seem quite 'specific' - heavily geared towards classical OO (think Java, or C++).

The one that I think is most useful, still, in 2021, is the most generic one (and the one that's least tied to "classic OO"):

"Single Responsibility"

I'd say that this one is (almost) always useful, regardless of the programming language or paradigm you're using (like OO, FP, and so on).

The more a principle is tied to the specific "classical OO" paradigm, the least relevant it still is in 2021. Well that's what I think :-)

Collapse
anicholson profile image
Andy Nicholson

These idea still apply in FP JS:

Every time we call connect() to wire up a React component to Redux or similar, we’ve just practiced dependency injection (props are injected dependencies) & single responsibility (component is not responsible for sourcing its own data).

Similarly, passing in callback functions to a component is following Open/Closed, as you’ve extended without modifying.

Collapse
leob profile image
leob

Great examples!

Collapse
andrewbaisden profile image
Andrew Baisden

Agreed well said.

Collapse
jackmellis profile image
Jack

As a FE developer, when I first learnt these principles in terms of real world use cases many years ago, it totally blew my mind.

I try to follow them as much as possible and I think my code has vastly improved as a result.

The front end ecosystem lends itself more to FP than OOP these days so much of the original SOLID descriptions are not really relevant, but there are ways to interpret them.

SRP and DI in particular are incredibly important principles that I take into consideration in everything I do.

Collapse
andreidascalu profile image
Andrei Dascalu

"we should be able to use a child and a parent class interchangeably" - no, not interchangeably. Liskov only states that a subclass should be usableable instead of the parent. Not necessarily the other way around. It's perfectly acceptable to add a method in a subclass.
In fact it's right there in the article you linked.

Collapse
huzaifa99 profile image
Collapse
sargalias profile image
Spyros Argalias • Edited

... I initially started by writing an answer that they are relevant and should be used if your language supports them. But I think it's more subtle than that.

For example, in front end, it's rare to apply these principles. Dependency injection seems rare (outside of the Angular framework). Hardcoded imports and usage of dependencies seems much more common. This breaks the open-closed principle in many cases.

Interfaces are not used either (JavaScript doesn't have them). Even when using TypeScript, interfaces are often used because they must be used for type checking, rather than to apply the dependency inversion principle.

But maybe it's okay. Each layer of abstraction has benefits, but also increases complexity. If the benefits aren't worth it, then maybe it's okay to ignore them. Dependency injection and interfaces aren't as useful if there is only ever a single implementation. There are ways to unit test without them and having to recompile isn't too bad.

But, my personal preference is to use them. I prefer Angular in this aspect compared to other front end frameworks. So in the end, I agree for the most part. I'd say to always use the single responsibility principle and the open-closed principle as much as possible. As for the rest, be pragmatic and start using them early if they'll provide a real benefit to the project.

Collapse
anicholson profile image
Andy Nicholson

I think the SOLID principles are just as relevant now as they’ve ever been, and FP languages still benefit from applying the values behind them.

The thing a lot of developers miss about SOLID: they’re about useful shapes for indirection & abstraction: they can’t tell you whether adding abstraction or indirection is the correct path to take in your setting & context.

As long-time OOer and modeling guru Sandi Metz says, “the wrong abstraction is more costly than no abstraction at all.”

And it’s precisely because introducing abstraction & indirection bear design cost that the SOLID principles are useful & relevant!

Collapse
phantas0s profile image
Matthieu Cneude

So:

  1. SRP: why only one purpose? It comes from more important principles information hiding and modules like decomposition, cohesion, and coupling.
  2. Open / Closed: this one is useless. It's from Bertrand Meyer and it only concerns inheritance, not interface implementation.
  3. Liskov Substitution Principle: this is from a paper by Barbary Liskov, and what she's saying is not what Martin (the one who came with the SOLID principle) is saying.
  4. Interface segregation: this is SRP applied to interfaces. It's not a principle.
  5. Dependency inversion principle: this one is quite useful.
Collapse
lyrod profile image
Lyrod • Edited

Part 4 : a class do not extends interfaces but classes. Typo of extends

Collapse
huzaifa99 profile image
Huzaifa Rasheed Author

My bad, I will fix it. Thanks 👍

Collapse
sauerbrei profile image
Sauerbrei

well written, well structured. thanks for this!

Collapse
andreidascalu profile image
Andrei Dascalu

Yangi => yagni

Collapse
csaltos profile image
Carlos Saltos

SOLID is very old by now, better give it a check to WET -> deconstructconf.com/2019/dan-abram...

Collapse
lluismf profile image
Lluís Josep Martínez

SOLID principles are simply good OO design practices, that exist since the 80s. Nothing new under the sun.