SOLID Principles — 2/2 — with code sample

In order to show how to properly SOLID-ify the code, we’ll use the following legacy — not so bad — code.

Let’s see what the code is intended to do :

UserSearchDemo.java
UserSearchClientTest.java
UserSearchServiceTest.java

Now let’s see the service :

UserSearchService.java
UserRepository.java
User.java
UserSearchCriteria.java

A client can use that UserSearchService :

UserSearchClient.java

How clean is the code ?

Let’s examine the code above. What do we observe ?

  • There is no way to test the display functionality
  • The UserSearchService is doing the search and display results
  • The display method UseSearchService with the switch block could be problematic
  • The search method in UseSearchService has too many lines of code

We can improve that piece of code, let’s first revisit SOLID principles.

Single Responsibility Principle — SRP

The UserSearchService class should have a single responsibility. Its purpose is to do user search by criteria, so then we must extract the displaying logic from there. Let’s see now what we have :

UserSearchService.java when complying to SRP
UserSearchService.java with finer SRP

We create ResultDisplayer class to comply to SRP

ResultDisplayer.java just created to comply to SRP

Now we have separated responsibilities, one is doing the search by criteria while the other displays the results of the search.

Open Close Principle — OCP

We can continue with the resulting refactored code from SRP. Actually we can display the results ordered by a natural order, or by lastname, or by age.

We want to add a new functionality : display the results of the search ordered by firstname.

Here we have to change the code by :

  • adding a BY_FIRSTNAME in the ORDER_TYPE enum
  • adding this code for the order BY_FIRSTNAME case.

Then we can use it :

ResultDisplayer.java

If we look at that class, when we want to add more ordering type, its display method will grow with more and more code. So it’s not complying with OCP because we keep modifying the existing code to extend the functionalities. To make it comply, we may use inheritance and polymorphism.

Let’s see what the ResultDisplayer implementation looks like :

ResultDisplayer.java with OCP and SRP

Now if we want to add another ordering type, e.g. by email, we just need to create a new class. And we won’t touch any existing code, except where we want to make a call to this new class.

Liskov Substitution Principle — LSP

As a result of the OCP work, we decided to decouple the logic of sorting and display. Then we created a method sort(List<User> users) and placed the result sorting logic there.

Let’s see what the ResultDisplayer implementation looks like :

ResultDisplayer.java

Do you see what is wrong? The ResultDisplayerNoOrder class has to implement the sort method, but there is no sorting logic.

ResultDisplayerNoOrder.java

By doing so, we are now in violation of Liskov substitution, because whenever we pass in one of the substituted ResultDisplayer to the service, the displaying behaviour is changed. One of the tests won’t pass anymore. To fix that, complying to LSP, we can have an empty implementation instead :

ResultDisplayerNoOrder.java

Now we can say our design is following the LSP. But still, it’s not so good. It’s kind of a patch. We can do it better and fix the problem using ISP and SRP, by identifying the abstraction and the responsibility separation method. Let’s continue with ISP.

Interface segregation Principle — ISP

Let’s change the design of classes and add interfaces on top of the ResultDisplayer class to make this more obvious to understand.. We are going to completely separate the concern of sorting from displaying.

ResultDisplayer.java ResultDisplayerConsole.java
ResultOrderer interface and it’s implementations

Now, if we need the result to be in some order or not, we can add an implementation of ResultOrderer, and use it :

After splitting the logic of sorting and displaying, we have 2 different interfaces ResultDisplayer defining the display method and ResultOrderer defining the sort method. That way, subclasses of ResultDisplayer don't have to implement the sort method. The same goes for ResultOrderer, it does not have to implement the display method.

Dependency Inversion Principle — DIP

Initially the ordering logic is done within the displaying. The displaying can be called whenever we need it, for instance, in the the UserSearchService, UserSearchClient and UserSearchDemo. But when calling the display method, the ordering is also done along with it, but in some cases we don't need ordering. If we look back to the legacy code of UserSearchService, we would add some code in there, and that service class would grow more and more. It would be harder to test and to make changes to.

For the two reasons above, we need to extract the sorting logic from the display logic. After we have extracted the logic and created the two interfaces ResultDisplayer and ResultOrderer, we have independent concerns. If we need to add any functionality related to the displaying or sorting, we just need to add an implementation of if as a subclass or in a specific method of ResultDisplayer or ResultOrderer.

Then we can inject the implementation at will. We can inject the ResultOrderer in ResultDisplayer, but we may not want to have this dependency. We may want to make a call when we really need it, meaning we may want display the list of users with or without ordering.

Let’s see what we have before doing all the dependency injection :

Code before dependency injection

The result of dependency injection

We can see the search method in the UserSearchService class make a call to the order method and then to the display method. We can do the same in the UserSearchClient class. And finally, in the main of the demo class, we can inject whatever the implementation we want. This provides a lot more usage flexibility.

Conclusion

Now we have a cleaner code, by using one or many of the SOLID principles at each step.

The first principle — SRP — is quite easy to comply with. The second — OCP — is a bit harder, but using polymorphism and inheritance helps. The third principles — LSP — is a lot harder to understand and to satisfy. The fourth principle — ISP — is not so complicated and we must use it when having abstractions so using it along with SRP could be beneficial. The last — DIP — is a familiar and regularly used. Without DIP, all the other principles would not be that powerful. It is important to take away that these principles are to be used together most of the time.

Github Repo: Kata SOLID principles

Acknowledgement

I would like to thank Dan MAGIER and Geoffroy VERGNE for reviewing and providing me insights, and James FLYNN to correct my English mistakes.

Unlisted

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kong To

Kong To

Developer, Coder or Craftsman, but code quality matters. Technical writer @wesquad @wemanity