SOLID Principles: The fundamental Object Oriented design principles


What does SOLID principles mean?

Proper planning prevents poor performance

You don’t want your software to have poor performance, do you? Do you want a poor design? An un-maintainable code base that other developers are scared to update because they can’t understand what the software is doing?

SOLID principles are a set of standard design principles that all software engineers should be familiar with and understand when building software.

As Hackernoon explains,

When the developer builds a software following the bad design, the code can become inflexible and more brittle, small changes in the software can result in bugs. For these reasons, we should follow SOLID Principles.

Hackernoon – SOLID Principles: A Simple and Easy Explanation

SOLID principles is an acronym which consists of 5 design principles:

Single Responsibility Principle

A class should have one and only one reason to change

You may often head the Single Responsibility Principles referred to as “a class should only have one responsibility.” Given the principle’s name, this description should be pretty self explanatory. But a better way to think about this principle is “a class should have only one reason to change.”

Every time you change or need to make an update to a class, you risk the possibility of breaking something inside that class. The more “things” that class does, the more chances there are for you to break something! You can mitigate this risk by creating smaller classes with a more focused, targeted responsibility.

I think Robert (Uncle Bob) Martin explains it best in his book Clean Code. I’ll paraphrase his description here on why creating more, smaller classes is better than creating a few, large classes:

If you have a collection of tools, would you prefer to have one large drawer where you throw all your tools in, or would you rather have your tools organized into many small drawers each containing well-defined labels?

By the way, if you haven’t yet, you should read his book. You can pick up a copy of it here:

Every software engineer must read this book >>

Open/ Closed Principle

Classes should be open for extension, but closed for modification

This one might be my favorite. Remember what we said before about the inherit risk in making a change to an existing class? Well here’s another principle to help mitigate that risk.

Software entities (classes, modules, functions, etc.) should be extendable without actually changing the contents of the class you’re extending. So how can we approach this?

Program to an interface, not an implementation

When programming to an interface and then writing classes that implement or abstract from that interface, we’re writing code so that we will be able to add new functionality without changing the existing code. This prevents situations in which a change to one of the classes requires us to adapt all depending classes.

Learning about polymorphism helps tremendously with this principle. By creating interfaces, we can create different implementations which can easily be substituted without changing the code that actually uses them!

The interfaces themselves are closed for modification, but we can provide new implementations to extends their functionality, they’re open for extension.

Liskov Substitution Principle

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

The hardest one to pronounce, but the easiest one to really understand. The Liskov Substitution Principle really just means that objects of a superclass should be able to be replaced with objects of its subclasses, without breaking the application.

That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

To help explain this concept better, I’ll use an example from the best Design Patterns book, Head First Design Patterns.

The best book to learn about Design Patterns >>

Let’s say you have an application to display birds. There are many different types of birds that need to be displayed, but we know for the most part, all birds are pretty similar, right?

So we create a base class called Bird. Since all birds can fly and tweet, we create these functions in the Bird base class, and any subclass of Bird will inherit these functions and override them if needed.

But what happens when we add a Bird of type Penguin? Penguins can’t fly, so we’d have to override the Bird’s fly function just to do nothing.

If an override method does nothing or just throws an exception, then you’re probably violating the LSP

tomdalling.com

Later in our application, we may have to interface with a bird’s flying function. But now since we have penguins that don’t fly, we have to take extra precaution to ensure that we’re not interfacing with birds of type Penguin.

And that’s they key violation to LSP. We want to interface with a Bird class (the base class), but we must first make sure the Bird isn’t of type Penguin first (its subclass).

To combat this LSP violation, you should learn about the Strategy Pattern.

Interface Segregation Principle

Clients should not be forced to depend upon interfaces that they do not use.

This one really boils down to keeping your interfaces focused on main core concept. Interfaces shouldn’t be huge, “super classes” that contain every possible solution. They shouldn’t do too many things, they should be focused enough on a Single Responsibility (but still flexible enough that other concrete classes still have the possibility to extend from it.

Dependency Inversion Principle

Depend upon abstractions. Do not depend upon concrete classes.

It’s always best to reduce dependencies in our classes. Why? Because software changes, constantly. Requirements, features, security patches.

The less dependencies our classes have, the less headaches we have, the less code change we need, the less maintenance.

Dependencies require detail and implementation logic. If our higher-level classes rely on those lower-level details, then our classes are tightly coupled and any change in the lower-level implementation details introduces risk for the higher-level classes. Instead, we should be relying on abstractions instead of the lower-level details themselves. (Note this is very similar above to the whole concept of “program to an interface, not an implmentation).

Robert Uncle Bob Martin explains in his book, Agile Software Development: Principles, Patterns, and Practices:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

Want to learn more Design Patterns for Object Oriented Code?

The best book to learn about Design Patterns >>

Adam Allard

Hi, I'm Adam Allard. I'm a Full Stack Software Engineer for Northrop Grumman creating web applications for the DoD. At this time I'm primarily working with Java and Angular based applications, although I have some years of experience in various languages and frameworks from previous jobs where I dabbled with Python & Django, C# & Xamarin, PHP, and Bootstrap. My hobbies include time with my family, wondering when the Green Bay Packers will win their next Super Bowl, drinking over-priced beer, and of course learning and teaching.

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts