The SOLID Principles in Software Design Explained
Tags: softwaredesign, csharp, architecture
Published: 2020-09-13 17:22:59

---
title: The SOLID Principles in Software Design Explained
published: true
date: 2020-06-21 18:18:45 UTC
tags: SoftwareDesign,CSharp,SoftwareArchitecture
canonical_url: https://eamonkeane.dev/solid-principles-in-software-design/
---

 ![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1563996588557-b969e2f2f757?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)

I planned on getting this finished earlier in the week but ended up shearing some of [our sheep](https://eamonkeane.dev/about/) 🐑. A few days late, whatever, let's get cracking.   
  
In this post you're going to learn about each of the **5 SOLID Principles**. I've included some code examples to make them a bit more real. I've also added some thought exercises / mental models that helped me understand these principles in the hope that they'll help you too.  
  
These principles are a subset of the principles promoted by  [Robert C. Martin](https://en.wikipedia.org/wiki/Robert_C._Martin).

<!--kg-card-begin: markdown-->
1. S - The Single Responsiblity Principle
2. O - The Open/Closed Principle (OCP)
3. L - The Liskov Substitution Principle (LSP)
4. I - The Interface Segregation Principle (ISP)
5. D - The Dependency Inversion Principle (DIP)
<!--kg-card-end: markdown-->


## 1. The Single Responsibility Principle (SRP)

![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1533574745199-4df19ce5c70b?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Markus Spiske</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

The single responsibility is defined as follows:

> Each class should should have RESPONSIBILITY over a single part of the functionality provided by the program.

What does this mean practically though? As a beginner programmer this isn't very helpful. Let's expand on the concept.

Examples of single responsibilities :

<!--kg-card-begin: markdown-->
- Validating inputs.
- Performing business logic.
- Saving and retrieving information to / from a database.
- Formatting a document.
- Performing calculations for the document.
<!--kg-card-end: markdown-->

So if you see a class that is validating inputs, logging events, reading and writing information to the database and performing business logic, you have a class with A LOT of responsibilities; violating the Single Responsibility Principle.  
  
How can you spot a class that may be violating the Single Responsibility Principle?

The class may have:

<!--kg-card-begin: markdown-->
- Tight coupling
- Low cohesion
- No seperation of concerns
<!--kg-card-end: markdown-->

**Tight coupling**  
Changing one class results in having to change a lot of other classes to get the program working again. Sound familiar? 😁

**Low Cohesion**  
The class contains fields and methods/functions that are unrelated to each other in any meaningful way.  
  
A good way to spot this is if methods in a class don't reuse the same fields. Each method is using different fields from the class.

**No Separation Of Concerns**  
Should my class that deals with validating an input be performing business logic and saving the data to the database? Not likely. Separate the program out into sections that deal with each concern.

A classic real world example of something having too many responsibilities are the multi function knives. They try to do too much and end up doing nothing well.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/Worlds_largest_Swiss_Army_knife_wenger_giant_knife.jpg)<figcaption>World's largest multi-tool. Cool to look at but probably doesn't do any one of it's functions really well. It has too many responsibilities.</figcaption>

* * *

## 2. The Open/Closed Principle (OCP)

![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1482859602406-7659b00979fb?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@khachiksimonian?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Khachik Simonian</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

> Software entities (classes, methods, modules) should be open for extension but closed for modification

**What does this mean in a practical sense?**   
  
You should be able to change the behaviour of a method without changing it's source code.  
  
For simple methods, adding / changing the logic in the method is perfectly reasonable. If you have to revisit this method 3+ times (not a hard number) due to requirements changing, you should start to think about the Open/Closed Principle.  
  
**Closing code to modification, why would you want to do this?**   
Code that we don't alter is less likely to create bugs due to unforeseen side effects.  
  
Here's an example of some code that is not closed for modification. We'll use a switch statement that will perform something different for each transport type.   
If a new transport type needs to be handled by our program then we need to modify the switch statement; violating the Open/Closed Principle.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/Open-Closed-Principle-Violation-1.png)<figcaption>A method that is violating the Open/Closed Principle</figcaption>

**So how can we achieve the Open Closed Principle in our code?**   
Typically we'd use

<!--kg-card-begin: markdown-->
- Parameters
- Inheritance
- Composition / Injection
<!--kg-card-end: markdown-->

**Using Parameters:**  
The _AddNumbersClosed_ method is not **Closed** for modification. If we have to alter the numbers that it's adding we have to change the method.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/Closed-method-OCP.png)<figcaption>A method that is not closed for modification. Violating the Open/Closed Principle.</figcaption>

The _AddNumbersOpen_ method is **Open** and extensible for situations where any two numbers need to be added. We don't need to _modify_ the method as long as we're adding two numbers. We can say that this method is closed for modification but open for extension.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/Open-method-OCP.png)<figcaption>A method that is closed for modification. It adheres to the Open/Closed Principle</figcaption>

**Using Inheritance:**

The _MakeSound()_ Method here is open for many different animals to make many different sounds.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/ocp-using-inheritance.png)<figcaption>Abiding to the Open Closed Principle using object inheritance. </figcaption>

**Using Composition / Injection:**  
  
In the following example, the responsibility for making the sound has been moved to the _SoundMaker_ Class.  
To add new behaviour, we could add a new class. This new class could provide some new behaviour to the _Dog c_lass.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/ocp-using-composition-and-injection.png)<figcaption>The <em>Dog</em> class showing how new behaviour can be added by using composition.</figcaption>

Why would you create a new class for new behaviour?

<!--kg-card-begin: markdown-->
- We know that stuff we've already built isn't affected.
- We can design the class to perfectly suit the new requirement.
- New behaviour can be added without interfering with old code.
<!--kg-card-end: markdown-->
* * *

## 3. The Liskov Substitution Principle (LSP)

![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1559715541-5daf8a0296d0?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@brett_jordan?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Brett Jordan</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

The Liskov Substitution Principle states that:

> Subtypes must be substitutable for their base types.

**Ok great, but what does that mean practically?**   
  
You may have learned about the 'is-a' relationship related to OOP inheritance.  
e.g. A dog 'is-a' animal (I know it should be _an_ animal, cut me some slack for demo purposes ).  
  
The Liskov Substitution Principle is basically stating that this 'is-a' relationship is not good enough for maintaining clean code. We should examine the relationship further and explore if we can slot in 'is-substitutable-for' instead.  
  
e.g. A dog 'is-substitutable-for' an animal. Can we say that we can substitute our dog for the animal?   
  
I'll show a classic example showing how the 'is-a' relationship can break down and cause some problems.  
  
It has a fantastic name; the rectangle-square problem.

Rectangle; 4 sides and 4 right angles.  
Square; 4 equal sides and 4 right angles.  
  
So.... A square 'is-a' rectangle.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/LSP-diagram.png)<figcaption>A square and a triangle. Wow</figcaption>

We have a _Rectangle_ class that could look something like this:

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/lsp---rectangle-class.png)<figcaption>The R<em>ectangle </em>class showing it's two properties; height and width.</figcaption>

And we have a square class that inherits from the rectangle class, because a square 'is-a' rectangle. This is what the square class looks like.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/lsp---square-class.png)<figcaption>The <em>Square</em> class that inherits from the <em>Rectangle</em> class as it obeys the 'is-a' relationship.</figcaption>

Now say we have a method that calculates the area of a rectangle. Should be pretty straightforward. We'll pass our rectangle as a parameter and return the width multiplied by the height.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/lsp-area-calculator.png)<figcaption>The <em>AreaCalculator</em> class</figcaption>

This  won't work when we have code like this:  
We create a new Rectangle.   
It has a _Height_ of 3 and a _Width_ of 2.

Our expected result is an area of 6, however, the actual result is 4.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/lsp-main-program.png)<figcaption>Showing how the 'is-a' relationship check can let you down.</figcaption>

**Why did this happen?**   
  
If you look at the code for the square class. When the width property is set, it overwrites the height. So we actually created a square with a width of 2 AND height of 2.   
  
This example is trivial and you can see that we instantiated a square. In real world programs this may not be as easy to spot. You might be receiving the object as a parameter from another class and not know it's type. This could lead to unintended results as shown above.

It comes down to our Square not being 'substitutable-for' a rectangle. A square's sides must be of equal length but a rectangles width and height can be different. We didn't perform the check before inheriting from the Rectangle class.

Some clues as to when your code is violating the LSP:

<!--kg-card-begin: markdown-->
- Type checking
- Null checks
- NotImplementedExceptions
<!--kg-card-end: markdown-->

You can also perform the duck test:

> If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction - Derick Bailey

* * *

## 4. The Interface Segregation Principle (ISP)

![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1573599451635-65ca56a9cdb3?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@willianjusten?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Willian Justen de Vasconcellos</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

What is the interface segregation principle?

> Clients should not be forced to depend upon interfaces that they do not use - [Bob Martin](https://en.wikipedia.org/wiki/Robert_C._Martin)

The client in this case is any **calling code**.

Take a look at this interface name _IPersonService_.  
It has three methods. Any client that implements this interface will have to implement these methods.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/interface.png)<figcaption>The IPersonService Interface</figcaption>

Now let's take this Child class that implements the _IPersonService_ Interface.   
  
For a child, the _SetSalary()_ method and the _Salary_ property do not make sense!  
  
The client (child class) depends on an interface that it does not use. ❌  
  
The interface is only partially implemented 😒.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/ISP---Child-1.png)<figcaption>The Child class implementing the IPersonService. It's depending on code that it doesn't use, namely the members related to Salary</figcaption>

See that method throwing the _NotImplementedException()_ ? It's a good sign that you're violating the Interface Segregation Principle.

This isn't so bad here, but it will become a problem with larger interfaces. It introduces higher coupling ( I like to think of high coupling as classes being super-glued together and tougher to separate). Future changes to the code will be more difficult.  
  
**How do we remedy this?**   
Split the interface into more _cohesive_ interfaces.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/interface-segregation-principle-cohesive-interfaces.png)<figcaption>Two new cohesive interfaces created by splitting up the IPersonService. </figcaption>

The _IPersonSalaryService_ is an interface that defines members related to a person's salary.

The _IPersonNameService_ does the same for members related to a person's name. Both are more cohesive than the original _IPersonService_.

Now our client code (_Child_ class) can depend on code that it actually uses. Much better. 😎

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/interface-segregation-principle-child-class.png)<figcaption>Child class depending on a more cohesive IPersonNameService</figcaption>

We can easily implement multiple classes in C# using this syntax. Take a look at this Adult class. It depends on code (the two interfaces) that it actually uses. ✅

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/interface-segregation-principle-adult-class.png)<figcaption>The Adult class implementing both the IPersonNameService and the IPersonSalaryService.</figcaption>

* * *

## 5. The Dependency Inversion Principle (DIP)

![The SOLID Principles in Software Design Explained](https://images.unsplash.com/photo-1531871165793-30177cc75a44?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=2000&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ)<figcaption>Photo by <a href="https://unsplash.com/@nadineshaabana?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Nadine Shaabana</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption>

The Dependency Inversion Principle states that

> "High-level modules should not depend on low level modules. Both should depend on abstractions.   
> Abstractions should not depend on details. Details should depend on abstractions"

<!--kg-card-begin: markdown--><iframe src="https://giphy.com/embed/iHe7mA9M9SsyQ" width="480" height="344" frameborder="0" allowfullscreen></iframe><!--kg-card-end: markdown-->

In a C# project, your project references will point in the direction your dependencies.   
  
In domain driven design or onion architecture as it's sometimes called, the references will point away from low-level (implementation code) and towards your business logic / domain layer.   
  
You can think of high-level code as being more process-orientated. It's more abstract and more concerned with the business rules.   
  
Low level code is the plumbing code.

Here's an example showing what high-level code and low-level code might look like in a program.

![The SOLID Principles in Software Design Explained](https://eamonkeane.dev/content/images/2020/06/Dependency-Inversion-Principle-diagram.png)<figcaption>Diagram showing what high-level and low-level code might look like. </figcaption>

The Domain layer is just concerned with publishing a course. As we move to lower level code we get more concrete. See changes to the course status id - this is low level code (In the context of a language like C#).  
  
Abstractions are generally achieved using interfaces and abstract base classes.

[@ardalis](https://twitter.com/ardalis) put it really well when he said that they're generally types that can't be instantiated (Read, you can't make a new object from them).

_Abstractions define a contract, they don't do the work_  
  
Abstractions specify **WHAT** should be done without telling us **HOW** they should be done.

Again, thinking of abstractions in terms of a contract is a useful exercise.

I like to think of an abstraction speaking to any class that depends on the abstraction as saying something like:

_ **"This is what you must do, I don't care how you do it."** -_ Abstraction speaking to a class that depends on it.   
  
This may seem stupid. It helps me understand abstractions and interfaces and how they can be used to make programs easier to design and manage.

> If it's stupid, but works, it ain't stupid!

* * *

So that's it, by now you'll have a better understanding of these 5 principles and you can start incorporating them into your work. You'll be aware of them if nothing else and being aware of them is half the battle.

I first learned about these principles in my University but what really drove them home was the SOLID Principles for C# developers course by [Steve Smith](https://ardalis.com/) on Pluralsight called 'SOLID Principles for C# Developers'. I highly recommend it.

If you have any questions pop them into the comment section below or reach out to me on [twitter where I post coding tips regularly](https://twitter.com/eamokeane).