I’ve seen too many projects which don’t adhere to SOLID simple principles. Usually, these projects build and work fine because « Hey! it’s our job… ». Making things work. But what happens when you must add a functionnality or correct a vicious bug with a project that does not follow SOLID ? A headache… Definitly.

SOLID is made of five simply principles. When they are applied to a project, they make reading and understanding the source code easier and, if you understand the source code, you can maintain it easily.

In this article, I’ll take a piece of code with bad conception and a great disrespect for SOLID principles. For each principle, I’ll explain the purpose and how to modify the code to comply with SOLID. Of course, the first version of the source code is awful and most of you will say « OMG! I can’t write such code!!! » but it’s just a blog article.

First of all, let’s talk about the sample. It’s a C# console app to manage car/dragster float. So we have a nice IVehicule interface which expose an engine and methods to move forward and backward. We also have implementations (Car and Dragster) and interface, enum and implementations for the engine used with the vehicules.

Source code can be found at here on GitHub.

Lets talk about SOLID

Like I’ve already said, SOLID is made of five principles writed by Robert C. Martin in the early 2000s for the object-oriented programming. These principles are :

  • S (Single reponsibility principle) : A class should have only a single responsibility.
  • O (Open/closed principle) : Software entities should be open for extension but closed for modification.
  • L (Liskov substitution principle) : Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
  • I (Interface segregation principle) : Many client-specific interfaces are better that one general-purpose interface.
  • D (Dependency inversion principle) : One should depend upon abstractions and do not depend upon concretions.

Okay, it’s a fairly abstract definition… Let’s delve into it!

Single responsibility principle

This principle says that each class must have one responsibility. By responsibility, we must understand a reason to change. In our example, the vehicule implementation have two responsibilities :

  • Defining the vehicule (moving, having an engine, …)
  • Displaying the movements as text

So now, if we want our vehicules to turn, we must modify our code. If we want to display movement in HTML format, we must modify our vehicules too. THIS is against S principle.

In order to respect this principle, we must separate the vehicule implementation from the movement display in two separate classes (each class will have only one responsibility). Our code should have been :

With this code, if we want to add the « Turn » functionnality to our vehicules, we must change the vehicule interface and implementations and if we want to change the render format, we must change the VehiculeOutputManager.

Open/closed principle

Let’s say we now want to have electrical engine. We create a new class to define the new engine and we add the Electric member in our EngineType enumeration. This respect the open of our principle. But we also need to modify the Car class to handle our new engine and THIS is against the closed principle. We should have used another way to decorrelate the instanciation of an engine in the vehicule constructors (a factory pattern, dependency injection, …) like this :

Liskov substitution principle

We have defined that a Dragster is a Car (inherits from) but a dragster cannot go backward. This breaks the Liskov substitution principle : if we create a Car variable instanciate with a Dragster instance (which is possible) and use the MoveBackward method, we’ll have an exception thrown in our code. In fact, a Dragster IS NOT a Car…

Hum… We still have the issue because our mistake is not in the fact that a Dragster was inheriting from a Car. It’s from the interface IVehicule that defined that a vehicule can go forward and backward… But this, we’ll see it in our next chapter…

Interface segregation principle

Like I said just before, our IVehicule is the perfect example to illustrate the Interface Segregation Principle. A vehicule cannot always go forward and backward like we’ve seen with the dragster (or with a bike). To have a clean source code, we should have splitted our IVehicule interface in three distinct interfaces. The first one will define that a vehicule have an engine and the two others will define that a vehicule can go forward or go backward :

With this modifications, we are sure that a Dragster cannot go backward.

Dependency inversion principle

This principle is illustrated in the VehiculeOutputManager class. This class have the responsibility to display vehicules movement in plain text or in HTML. Now that a « Dragster is not a Car », we have a lot of methods : moving forward and backward in plain text and HTML for Car and Dragster… And tomorrow, when we’ll add new vehicules, we must create new display methods… To correct this, instead of using implementations in method parameters, we’ll use interfaces like this :

With that, whichever vehicule implementation we add, we can display its movements without modifying our source code.

What’s next

These five principles help us to have more readable and easier to maintain source code. But in larger scale, respecting these principles in bigger projects leads us to use others patterns.

For example, the layer separation pattern is used to be sure that the data access layer (first responsibility) is not embedded with the business logic (other responsibility). Same for the service layer or the presentation layer (respect of S principle).

In most cases, the layer separation is used with a dependency injection pattern to facilitate the replacement of a layer (respect of I and D principles).

But this will be another blog post 🙂

Stay tuned.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.