DEV Community

Cover image for SOLID: Software Design Principles. Be a better developer
BOX PIPER
BOX PIPER

Posted on • Edited on • Originally published at boxpiper.com

SOLID: Software Design Principles. Be a better developer

S.O.L.I.D design principle comes from Object oriented programming guidelines. It is designed to develop software that can be easily maintained and extended; prevents code smells; easy to refractor; promotes agility and finally incorporates rapid + frequent changes quickly without bugs.

Generally, technical debt is the result of prioritizing speedy delivery over perfect code. To keep it under control - use SOLID principles, during development.

Robert Martin, is credited with writing the SOLID principles and stated 4 major software issues if S.O.L.I.D is not followed diligently. They are :

  • Rigidity:

    • Implementing even a small change is difficult since it's likely to translate into a cascade of changes.
  • Fragility:

    • Any change tends to break the software in many places, even in areas not conceptually related to the change.
  • Immobility:

    • We're unable to reuse modules from other projects or within the same project because those modules have lots of dependencies.
  • Viscosity:

    • Difficult to implement new features the right way.

SOLID is a guideline and not a rule. It is important to understand the crux of it and incorporate it with a crisp judgement. There can be a case when only few principles out of all is required.

S.O.L.I.D stands for:

  • Single Responsibility Principle (SRP);
  • Open Closed Principle (OCP);
  • Liskov Substitution Principle (LSP);
  • Interface Segregation Principle (ISP);
  • Dependency Inversion Principle (DIP);

Single Responsibility Principle (SRP)

Every function, class or module should have one, and only one reason to change, implies should have only one job and encapsulate within the class (stronger cohesion within the class).

It supports "Separation of concerns" — do one thing, and do it well!"

Sidenote: Originally this article is published on Box Piper source.

For example, consider this class:

class Menu {
  constructor(dish: string) {}
  getDishName() {}
  saveDish(a: Dish) {}
}
Enter fullscreen mode Exit fullscreen mode

This class violates SRP. Here is why. It is managing the properties of menu and also handling the database. If there is any update in database management functions then it will affect the properties management functions as well, hence resulting in coupling.

More cohesive and Less Coupled class Instance.

// Responsible for menu management
class Menu {
  constructor(dish: string) {}
  getDishName() {}
}

// Responsible for Menu management
class MenuDB {
  getDishes(a: Dish) {}
  saveDishes(a: Dish) {}
}
Enter fullscreen mode Exit fullscreen mode

Open Closed Principle (OCP)

Classes, functions, or modules should be opened for extensibility, but closed for modification.
If you created and published a class - changes in this class, it can break the implementation of those, who are started using this class. Abstraction is the key to getting OCP right.

For example, consider this class:

class Menu {
  constructor(dish: string) {}
  getDishName() {}
}
Enter fullscreen mode Exit fullscreen mode

We want to iterate through a list of dishes and return their cuisine.

class Menu {
constructor(dish: string){ }
getDishName() { // ... }

    getCuisines(dishName) {
      for(let index = 0; index <= dishName.length; index++) {
         if(dishName[index].name === "Burrito") {
            console.log("Mexican");
         }
         else if(dishName[index].name === "Pizza") {
            console.log("Italian");
         }
      }
    }

}
Enter fullscreen mode Exit fullscreen mode

The function getCuisines() does not meet the open-closed principle because it cannot be closed against new kind of dishes.

If we add a new dish say Croissant, we need to change the function and add the new code like this.

class Menu {
constructor(dish: string){ }
getDishName() { // ... }

    getCuisines(dishName) {
      for(let index = 0; index <= dishName.length; index++) {
         if(dishName[index].name === "Burrito") {
            console.log("Mexican");
         }
         if(dishName[index].name === "Pizza") {
            console.log("Italian");
         }
         if(dishName[index].name === "Croissant") {
            console.log("French");
         }
      }
    }

}
Enter fullscreen mode Exit fullscreen mode

If you observe, for every new dish, a new logic is added to the getCuisines() function. As per the open-closed principle, the function should be open for extension, not modification.

Here is how we can make the codebase meets the standard to OCP.

class Menu {
  constructor(dish: string) {}
  getCuisines() {}
}

class Burrito extends Menu {
  getCuisine() {
    return "Mexican";
  }
}

class Pizza extends Menu {
  getCuisine() {
    return "Italian";
  }
}

class Croissant extends Menu {
  getCuisine() {
    return "French";
  }
}

function getCuisines(a: Array<dishes>) {
  for (let index = 0; index <= a.length; index++) {
    console.log(a[index].getCuisine());
  }
}

getCuisines(dishes);
Enter fullscreen mode Exit fullscreen mode

This way we do not need to modify the code whenever a new dish is required to add. We can just create a class and extends it with the base class.

Liskov Substitution Principle (LSP)

A sub-class must be substitutable for their base type, states that we can substitute a subclass for its base class without affecting behaviour and hence helps us conform to the "is-a" relationship.

In other words, subclasses must fulfil a contract defined by the base class. In this sense, it's related to Design by Contract that was first described by Bertrand Meyer .

For example, Menu has a function getCuisines which is used by Burrito, Pizza, Croissant and didn't created individual functions.

class Menu {
  constructor(dish: string) {}
  getCuisines(cuisineName: string) {
    return cuisineName;
  }
}

class Burrito extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

class Pizza extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

class Croissant extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

const burrito = new Burrito();
const pizza = new Pizza();
burrito.getCuisines(burrito.cuisine);
pizza.getCuisines(pizza.cuisine);
Enter fullscreen mode Exit fullscreen mode

Interface Segregation Principle (ISP)

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

The "interface" word in the principle name does not strictly mean an interface, it could be an abstract class .

For example

interface ICuisines {
  mexican();
  italian();
  french();
}

class Burrito implements ICuisines {
  mexican() {}
  italian() {}
  french() {}
}
Enter fullscreen mode Exit fullscreen mode

If we add a new method in the interface, all the other classes must declare that method or error will be thrown.

To solve it

interface BurritoCuisine {
  mexican();
}
interface PizzaCuisine {
  italian();
}

class Burrito implements BurritoCuisine {
  mexican();
}
Enter fullscreen mode Exit fullscreen mode

Many client-specific interfaces are better than one general-purpose interface.

Dependency Inversion Principle (DIP)

Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, decouple them and make use of abstractions.

High-level modules are part of an application that solve real problems and use cases.
They are more abstract and map to the business domain (business logic);
They tell us what the software should do (not how, but what);

Low-level modules contain implementation details that are required to execute the business policies; About how the software should do various tasks;

For Example

const pool = mysql.createPool({});
class MenuDB {
  constructor(private db: pool) {}
  saveDishes() {
    this.db.save();
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, class MenuDB is a high-level component whereas a pool variable is a low-level component. To solve it, we can separate Connection instance.

interface Connection {
  mysql.createPool({})
}

class MenuDB {
   constructor(private db: Connection) {}
   saveDishes() {
      this.db.save();
   }
}
Enter fullscreen mode Exit fullscreen mode

Ending Note

Code that follows S.O.L.I.D. principles can be easily shared, extended, modified, tested, and refactored without any problems. With each real-world application of these principles benefits of the guidelines will become more apparent.

Anti-patterns and improper understanding can lead to STUPID code: Singleton, Tight Coupling, Un-testability, Premature Optimization, In-descriptive Naming, and Duplication. SOLID can help developers stay clear of these.

To read more such interesting topics, follow and read BoxPiper blog.

Support my work and buy me a Coffee. It'll mean the world to me. 😇

Top comments (11)

Collapse
 
frondor profile image
Federico Vázquez

Don't use DEV to lure people into your business. Either post the full article or don't do it.

Collapse
 
kuba86 profile image
Jakub Blaszczyk • Edited

Fully agree, they will only trash the community 🙄
For everyone who feels the same please report the user for violation of dev.to/terms

"11. Content Policy
The following policy applies to comments, articles, and all other works shared on the DEV platform:

Users must make a good-faith effort to share content that is on-topic, of high-quality, and is not designed primarily for the purposes of promotion or creating backlinks.
Posts must contain substantial content — they may not merely reference an external link that contains the full post."

Collapse
 
boxpiperapp profile image
BOX PIPER

I have added a complete article.
Requests community to avoid being harsh to Box Piper.

P.S. Box Piper is not even a month old, born on Sept 2020 with the sole aim to provide high-quality content on Tech, Product, Money, Books, Life.

Collapse
 
boxpiperapp profile image
BOX PIPER

Firstly, Box Piper is not a business but a blog for Tech, Product, Money, Books, Life.
Secondly thanks for the feedback. Noted.

Collapse
 
drawcard profile image
Drawcard

I would really like to read the rest of this article, but I don't support the idea of posting half an article, just to attract clicks to your site. If you could share the entire article here that would be really appreciated :)

Collapse
 
boxpiperapp profile image
BOX PIPER

Shared complete article here.
Thanks for feedback.

Collapse
 
drawcard profile image
Drawcard

Thank you!

Collapse
 
szilardszabo profile image
SS • Edited

I think the problem is with SOLID that it assumes that perfect software can be created from scratch using these few vary simple and basic principles. Which is of course not the case, actually far from it! In reality, optimizing code and some of its qualities is a highly iterative process, and the optimization principles lies in the way that these iterations are done, the main point being what to optimize and when. You do not start writing code by optimizing, and you do not write optimized code from scratch. What needs to be de-coupling is actually the coding from the optimization. First you code to create solution, than if your solution survives and lives in a longer term than you start optimistic, and you only optimize what is practical as it is a very time consuming and resource hungry process. Good experienced developer knows that it is never a good idea to mix coding with optimization and when it comes to coding the outcome that matters is working code, not optimized code, so it does not matter if it is extensible or re-usable until this point. I think there is a more important rule that says "early optimization is the root of evil" and SOLID goes again this very old and essential rule, as it tries to suggest early optimization that is often not needed and even outright harmful. From these examples here you can also see how these optimizations result in much more code lines than it needed for a simple solution. So SOLID result in more verbose, therefore more complex code even if it is not necessarily needed. Creating and optimizing code needs much more than written down in these few principles and that knowledge only comes with experience in building lot of software. This is another mistake in SOLID that it tries to suggest that you can create optimal software without lot of experience just by following these few simple rules which is a huge error. Do not misunderstand me these are good principles but what is more important to understand the process to create good software which is a iterative process, and it depends on the life cycle of the code what optimization is needed. So in some stage - for example in a very early one - it is not even a good idea to follow these principles as it would result in unneeded early optimization creating over-complicated verbose code for a simple task. So all-in-all whoever put these principles down lacked the real experience to understand that the problem he is dealing with more complex than he thought. Creating optimal code is not a one step process sing some simple principles, it is all about what to do in what stage of the software and it is much more complex than these few simple principles. Lot of the time there is absolutely no problem at all with a simple and not over-optimized code even if it goes against SOLID, as in different life cycle of the code there are different priorities. E.g. simplicity in an early stage, starting solution is much more important than extensibility Related to single responsibility, sometimes it is better to have one function / class that does everything - especially if it is a lot - than splitting it up into to countless classes making your code longer, more complex and understand. also some rules that not in SOLID: only create re-usable code if you are sure you are going to re-use it, otherwise it is a waste of time. Only create extensible class if you are sure it is needed, and you are going to extend it, otherwise it is a waste of time, resources and also make your code longer and more complex than it is needed. Only beginners think that only by using SOLID principles you need to create optimal code from scratch in one huge step regardless if you need that or not. Only beginners think that you need to make your code following all these principles in the first step even if it is just a few simple function, making it re-usable and extensible just in case... Even without being sure that it will be ever re-used and / or extended. So in itself these simple examples that you can see everywhere related to SOLID principles are wrong, as these principles most of the time only come in to sight when you are dealing with huge software with a need of refactoring so the examples should reflect this fact. Illustrating SOLID principles with these simple examples suggest to beginners that they have to over optimize every software even with only 10 lines of code from day one, and unfortunately you can even see this happening everywhere in the industry by beginners in reality everywhere, and they think this how you create "good" code which is ridiculous. Solid principles must only come in the picture in relation of refacturing bigger code bas, but in that case there are much more to follow than these few simple rules, and to know the rest only comes with experience and you need to familiarize yourself with much bigger amount of terminologies from more serious authors. I think the main error in SOLID and the way it is represented that it actually tries to suggest that you need to mix coding with optimization even from the start, suggesting that optimization is as much, if not more, important than working code in the phase of coding when creating a working solution comes first. code optimization in terms of code quality must be a seaprate process from coding phase! These two should not be mixed and should represent different phase of the development cycle. Lot of companies misunderstand and do not know these basic principles and enforce developers to create high quality optimized working code from scratch, which is actually not even possible, not even using these few simple SOLID principles of course. When companies do this, then this is all about saving money and nothing else, and actually this is one of SOLID great error not to be clear on that point so that their principles can not be misrepresented by this way. I am not even sure that whoever created SOLID principles did not only created it to convince beginner developers to mix coding and optimization phasing into one single phase, which is wrong and all about just saving money for software companies by spending less time and money on development and actually creating worst software by using un-needed early optimization "just in case". It is like saying to the develpper: "I am paying so do everything at once for my money..." Unfortunately in practice I can see SOLID interpreted this way actually enforcing really bad practices on developers. The other thing is that only using SOLID principles does not make your code that much better as you need to know much more than these simple basic principles to create real good quality code. So this is another very bad aspect of SOLID as it tries to make it beleive that these few simple principles that you need to know and that is it, voila you have the optimal code. :-(

Collapse
 
boxpiperapp profile image
BOX PIPER • Edited

Thanks for your amazing analysis. It really blew apart lot of things.
Indeed you are correct, agreeable to lot of points, and thanks for taking time out to explain it so beautifully.

I did mention elephant in the room by saying:
SOLID is a guideline and not a rule. It is important to understand the crux of it and incorporate it with a crisp judgement. There can be a case when only few principles out of all is required.

Nevertheless thanks again

Collapse
 
szilardszabo profile image
SS • Edited

What frustrates the most in SOLID principles that it instills a sense of inferiority in seasoned developers. It suggest that it is very little that you need to master to be able to write good software. It is the opposite, you need to know much much more that only comes with a lot of years of experience and you need to learn from sources really different from people who wrote SOLID. Also if I think about these SOLID principles more, I can come up with a tons of examples that contradict every one of them! So whoever writes principles like these should make sure that at least they are much more SOLID than SOLID! For example if I have 100 things to do then I just have to create 100 different functions, just to make sure every single function does only 1 single thing??? WRONG! Truth is you have to split your 100 things into categories and implement as many functions as categories. This is called separation of concerns. To do this right you need a lot of experience and maybe the right education. How many functions you need is a trade off between so many things. Efficiency, performance, productivity, simplicity, maintainability, extensibility etc.. It is far from an easy task and you hardly ever end up with 100 functions doing 1 single thing each. So there is no such thing as "single responsibility principle", most functions in a real world application have multiple responsibility. In SOLID they oversimplify things, and instead of saying that you need to separate concerns and decide between trade offs that takes a lot of experience, they say just create as many functions as things you do, and voila you do not need experience, you need no proper education in the field and just knowing these 5 single "principle" you end up with the optimal software. So who needs experienced, educated developers anymore? This can't be further from the truth. It is actually just plain stupidity.

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

If you'd like to see examples on PHP look at My post dev.to/evrtrabajo/solid-in-php-d8e