SOLID is not a state

Pablo Caballero
BestSecret Tech
Published in
9 min readJun 7, 2022

A typical question that I see in many technical interviews is “tell me something about SOLID principles”. Here we can see three types of candidates, the first one begin wearing poker face, the second type has heard about the topic but they repeat the theory as a parrot and the third one is a white blackbird.

Blackbird
Photo by Stephen Tafra on Unsplash

What is a principle?

First of all, I’d like to reference to the definition of principle in Cambridge dictionary.

If you agree with or believe something in principle, you agree with the idea in general, although you might not support it in reality or in every situation.

So, the first thing in mind could be… — Can we break them? — Yes, we can and sometimes we have to do it. Hence, the second question comes… — When should we do it? It isn’t a stupid question, but maybe a stupid answer and a song (by Jarabe de Palo) comes to my mind. — As everything in life it depends on… As an experienced software engineer you should balance the risk of breaking one of them and a possible overengineering. In any case try to follow these ideas.

Consequently, we’ve a list of ideas to make our life easier and that is good for our colleges as well. One effect would be having a cleaner code.

Let’s do it

Acronyms
Photo by Sven Brandsma on Unsplash

SOLID is an acronym, this acronym is for 5 principles:

  • Single Responsibility Principle (SRP).
  • Open-Closed Principle (OCP).
  • Liskov’s Substitution Principle (LSP).
  • Interface Segregation Principle (ISP).
  • Dependency Inversion Principle (DIP).

Single Responsibility Principle

There should never be more than one reason for a class to change.

Swiss army knife
Idea: What shouldn’t not be done.
Robert C. Martin

It was introduced in 2003 by the software engineer Robert C. Martin or just “Uncle Bob” in his book “Agile Software Development, Principles, Patterns, and Practice” [1].

Nobody wants to remake a class, so if business wants another feature or functionality the development team should reduce the number of changes. Bear in mind business can change their mind like a weather vane.

One recommendation is using domain-driven-design (DDD) or any kind of layered architecture, so the developer has an idea where the code should be otherwise some it’s a chaos (or a spaghetti code).

C. L. “Kelly” Johnson

Ergo this principle I would summed it up in “KISS principle” [2] introduced by the engineer Kelly Johnson: Keep It Simple Stupid. Honestly, we should apply it in our life…

Use case 1: We’re making a class and we need its loader, then we create a method or a constructor to load it… Later we receive a new functional requirement, so it will be loaded in another way and hence we will have to change it again. However the real functionality of our class remains.

The class Employee breaks SRP.

Thus, we could split it in some specific classes:

Use case 2: When we’re applying DDD, it’s really common merging the application layer (an orchestrator) and the domain layer (the business logic)… At the beginning it could be fine when we just have two methods and a constructor. However, we shouldn’t forget to split it when the size is bigger than a little “monster”.

Benefits:

  • Decoupling. Instead of having one class that does everything we have multiple parts of a complex organism.
  • A lot of responsibilities then a lot of reasons to change it.
  • Easier to read. The code should be readable, we’re not in the assembler ages.
  • Easier to maintain. More classes with less code.
  • Easier testing. We’ve simpler classes to test.

Drawbacks:

  • Outbreak of classes (currently the space is not a problem), a well defined architecture is needed.

Open-Closed Principle

Software entities (classes , modules , functions , etc .) should be open for extension , but closed for modification

Dr. Bertrand Mayer
Dr. Bertrand Mayer

It was introduced in 1988 by Dr. Bertrand Mayer in his book “Object Oriented Software Construction” [3].

1️⃣ Open for extension

  • The behaviour of a module can be extended to meet the new requirements.

2️⃣ Closed for modification

  • The source code of that module is inviolate.
  • A program cannot be 100% closed, we have to find a strategy to modify it.

How can we do?

  • Abstraction. We should detect the required properties and behaviours of an object to differentiate it from other objects.
  • Polymorphism.[] Our objects should inherit from other more generic and then we can reuse attributes and methods from other classes.

Polymorphism (extra)

  • Method/Constructor overloading: Same name with different arguments.
  • Method overriding: Same name and same arguments. It replaces the inherited implementation.
  • [Overriding in Java] The method has a new implementation than the parent class. In the parent class the method has not to be marked as final.
  • [Overriding in C#] The parent class the method has to be marked as virtual and in our class the method has to be marked as override.

Use case 3: We’ve several ways to do a method. So we could call to a specific method depending on an instance variable…

Vehicle has several functionalities depending on the property type.

In the following method we’ve the same functionality:

Defined 3 classes in Java in order to follow open-closed principle.

➕ Benefits:

  • Easier to maintain.
  • Reusable.

Drawbacks:

  • Some effort to change the mentality to object-oriented programming.

Liskov’s Substitution Principle

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Dr. Barbara H. Liskov & Dr. Jeannette M. Wing
Dr. Barbara H. Liskov & Dr. Jeannette M. Wing

It was introduced in 1994 by Dr. Barbara H. Liskov and Dr. Jeannette M. Wing [4]. Later in 1996, Robert C. Martin officially the principle was named.

In my opinion, current object oriented programming languages ease developers to don’t think about this principle… We always can see exceptions…

👩‍🎓 Summing up:

S is a subtype of T ➡ Objects of type T may be replaced with objects of type S

Use case 4: We’ve applied inheritance, however we are overriding a behaviour…

The functionality is overridden (polymorphism) to provide new functionality.

We could create a base class to define a behaviour, and one implementation per class.

The previous classes have been split in 3 classes. ParallelogramListusage.java is using them.

In the previous examples the method calculateArea() is defined once and used in both classes due to the inheritance. We’re applying this principle to the list as well in combination with ISP.

➕ Benefits:

  • Easier to maintain. And code reviews are easier with smaller files…
  • Reusable. The common code goes to parent classes.

➖ Drawbacks:

  • Some effort to change the mentality to object-oriented programming.

Interface Segregation Principle

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

Use case 5: We’ve been grouping all the functionality in the same interface but some methods are not supported by all the classes… A bad approach is throwing an exception.

CD class doesn’t support a method so it raises an exception.

The interface IProduct can be split in 2 or more specific interfaces, so you implement what you have.

IProduct has been divided in 2 interfaces.

Use case 6: A common example is the implementation of the collections in Java (List, Collection or Iterable) and in .NET (ICollection, IEnumerable or IQueriable) as well.

➕ Benefits:

  • Easier to maintain. The number of methods are reduced, you should have what you need and nothing more.
  • Reduced coupling. The usage if coupled to a contract.
  • Reduce “fat” interfaces. Clients should not be forced to depend upon interfaces that they do not use.

➖ Drawbacks:

  • Explosion of interfaces. A difficult question comes to my mind… — When should we stop splitting them? I have no answer…

Dependency Inversion Principle

High level modules should not depend upon low level modules. Both should depend upon abstractions.

Abstractions should not depend upon details . Details should depend upon abstractions.

It was introduced by Rober C. Martin in 2003.

At this point, you should know (or not) what is an interface. An interface is a contract [5]. So you depend on what you need, you don’t depend on how to do it.

Why?

  • Rigidity. One change can affect many parts due to those specific implementations.
  • Fragility. Unexpected errors happens because the developer could forget one of the dependencies (passing a null) or multiple implementations of classes with small behavioural differences…
  • Immobility. It’s hard to reuse, we have constructions of classes everywhere. It’s really hard to test it or mock it.

How?

  • Layering. Multiple layers and every layer has their responsibility. A common design pattern can be followed such as Domain Driven Development or Hexagon Architecture [6].
  • Separate interface from implementation.
  • Finding the underlying abstraction.

Is IoC the same as DI?

A typical mistake about this principle is thinking that the inversion of control (IoC) is the same as dependency injection (DI).

  • IoC. It’s a feature of the frameworks [7]. The system calls me instead of my code calling to the framework. It’s the Hollywood principle:

Don’t call us, we’ll call you.

  • DI. It’s a form of IoC where an object is passed through constructors/setters/services.
  • IoC without DI. E.g. a template pattern where the implementation changes throught subclassing.

Use case 7: We need to access to more specific services either repositories or other services.

ShoppingBag breaks DI principle.

The services should be injected before using them. In this example the service IPaymentMethod and the repository IRepository are passed in the constructor of ShoppingBag.

ShoppingBag using DI.

Use case 8: Example of ServiceLocator as example IoC without DI.

The previous class ServiceLocator is a simple container that maintains a list of implementations and those services can be requested wherever you need. Usually the dependencies are initialized in the constructor of the class.

In Spring Framework, IoC can be defined using an annotation mechanism or an xml configuration [8]. It isn’t the only one, there are also other libraries such as PicoContainer or Google Guice.

In ASP.NET Core, with IServiceCollection defined in Microsoft.Extensions.DependencyInjection [9] allows you to define the IoC. For older versions there were multiple libraries such as AutoFac, NInject, Unity, Castle Windsor

➕ Benefits:

  • Maintainability. The coupling is reduced between layer/modules.
  • Easier testing. We can create mocks, otherwise we’re coupled to the external services.
  • Readability. You only declare what you need and then you just use them.

➖ Drawbacks:

  • Non supported IoC. In system without native IoC, we’ve to implement it or spend time creating a way to use it.
  • Multiple implementations. Sometimes there are multiple implementations for the same interface so you need a way to select unequivocally the desired class.

Than you for your attention

Photo by Wilhelm Gunkel on Unsplash

Please don’t hesitate to contact me if you want to talk about this or anything else.

How to cite this story

Caballero, P. (2022, June). SOLID is not a state. Medium. Retrieved from https://medium.com/p/720795e57cf6

Acknowledgments

I would like to acknowledge De Jerónimo, J. A. and Byle, D. for their assistance with review of this story.

References

[1]: Winter, R.J. (2014). Agile Software Development: Principles, Patterns, and Practices. Perf. Improv., 53: 43–46. DOI: https://doi.org/10.1002/pfi.21408

[2]: Rich, B. (1995), 231. Clarence L. Johnson. National Academies Press. Washington D.C.. Retrieved from http://www.nasonline.org/publications/biographical-memoirs/memoir-pdfs/johnson-clarence.pdf

[3]: America, P. (1989). Object-oriented software construction. Science of Computer Programming, 12(1), 88–90. DOI: https://doi.org/10.1016/0167-6423(89)90034-8

[4]: Liskov, B. and Wing, J. (1994). A behavioral notion of subtyping. ACM Trans. Program. Lang. Syst. 16, 6 (Nov. 1994), 1811–1841. DOI: https://doi.org/10.1145/197320.197383

[5]: Microsoft Doc (2022). interface-C# Reference. Retrieved from https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface

[6]: Stenberg, J. (2014). Exploring the Hexagonal Architecture. InfoQ. Retrieved from https://www.infoq.com/news/2014/10/exploring-hexagonal-architecture/

[7]: Fowler, M. (2005). InversionOfControl. Retrieved from https://martinfowler.com/bliki/InversionOfControl.html

[8]: The IoC container. Spring Framework Reference Documentation. Retrieved from https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html

[9]: Larkin, K. and Smith, S. and Dahler, B. (2022). Dependency injection in ASP.NET Core. Microsoft Doc. Retrieved from https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0

This story (it isn’t a paper) provided by Pablo Caballero on Medium is for general information purposes only. All information has been provided in good faith, however I make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability or completeness of any information on this story.

--

--