Refactoring Legacy Code: Part 8 - Inverting Dependencies for a Clean Architecture

Old code. Ugly code. Complicated code. Spaghetti code. Gibberish nonsense. In two words, Legacy Code. This is a series that will help you work and deal with it.

It's now time to talk about architecture and how we organize our newly found layers of code. It's time to take our application and try to map it to theoretical architectural design.

Clean Architecture

This is something we've seen throughout our articles and tutorials. Clean architecture.

At a high level, it looks like the schema above and I am sure you are already familiar with it. It is, a proposed architectural solution by Robert C. Martin.

In the center of our architecture is our business logic. These are the classes representing the business processes our application tries to solve. These are the entities and interactions representing the domain of our problem.

Then, there are several other types of modules or classes around our business logic. These can be seen as simple helping auxiliary modules. They have various purposes and most of them are indispensable. They provide the connection between the user and our application through a delivery mechanism. In our case, this is a command line interface. There is another set of auxiliary classes which are connecting our business logic to our persistence layer and to all the data in that layer, but we don't have such a layer in our application. Then there are helping classes like factories and builders which are constructing and providing new objects to our business logic. Finally there are the classes representing the entry point to our system. In our case, GameRunner can be considered such a class, or all of our tests are also entry points in their own way.

What is most important to notice on the diagram, is the dependency direction. All the auxiliary classes depend on the business logic. The business logic does not depend on anything else. If all the objects in our business logic could magically appear, with all the data in them, and we could see whatever happens inside our computer directly, they should be able to function. Our business logic must be able to function without a UI or without a persistence layer. Our business logic must exist isolated, in a bubble of a logical universe.

The Dependency Inversion Principle

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.

This is it, the last SOLID principle and probably the one with the greatest effect on your code. It is both pretty simple to understand and pretty simple to implement.

In simple terms, it says that concrete things should always depend on abstract things. Your database is very concrete, so it should depend on something more abstract. Your UI is very concrete, so it should depend on something more abstract. Your factories are very concrete again. But what about your business logic. Inside your business logic you should continue applying these ideas, so that the classes that are closer to the boundaries are depending on classes that are more abstract, more at the heart of your business logic.

A pure business logic, represents in an abstract way, the processes and behaviors of a defined domain or business model. Such a business logic does not contain specifics (concrete things) like values, money, account names, passwords, the size of a button or the number of fields in a form. The business logic shouldn't care about concrete things. It should only care about your business processes.

The Technical Trick

So, the Dependency Inversion Principle (DIP) says we should invert our dependencies whenever there is code that depends on something concrete. Right now our dependency structure looks like this.

GameRunner, using the functions in RunnerFunctions.php is creating a Game class and then uses it. On the other hand, our Game class, representing our business logic, creates and uses a Display object.

So, the runner depends on our business logic. That is correct. On the other hand, our Game depends on Display, which is not good. Our business logic should never depend on our presentation.

The simplest technical trick we can do is to make use of the abstract constructs in our programming language. A traditional class is more concrete than an abstract class, which is more concrete than an interface.

An Abstract Class is a special type that can not be initialized. It contains only definitions and partial implementations . An abstract base class usually has several child classes. These child classes are inheriting the common partial functionality from the abstract parent, they are adding their own extended behavior, and they must implement all the methods defined in the abstract parent but not implemented in it.

An Interface is a special type that allows only the definition of methods and variables. It is the most abstract construct in object oriented programming. Any implementation must always implement all the methods of its parent interface. A concrete class can implement several interfaces.

Except for the C family object oriented languages, the others like Java or PHP do not allow multiple inheritance. So a concrete class can extend a single abstract class but it can implement several interfaces, even at the same time if needed. Or put from an another perspective, an single abstract class can have many implementations, while many interfaces can have many implementations.

For a more complete explanation of the DIP, please read the tutorial dedicated to this SOLID principle.

Inverting Dependency Using an Interface

PHP fully supports interfaces. Starting from the Display class as our model, we could define an interface with the public methods all classes responsible with displaying data will need to implement.

Looking at Display's list of methods, there are 12 public methods, including the constructor. This is quite a large interface, you should keep this number as low as possible, exposing interfaces as clients need them. The Interface Segregation Principle has some good ideas about this. Maybe we will try to deal with this problem in a future tutorial.

What we want to achieve now is an architecture like the one below.

This way, instead of Game depending on the more concrete Display, they both depend on the very abstract interface. Game uses the interface, while Display implements it.

Naming Interfaces

Phil Karlton said, "There are only two hard things in Computer Science: cache invalidation and naming things."

While we do not care about caches, we need to name our classes, variables, and methods. Naming interfaces can be quite a challenge.

In the old days of the Hungarian Notation, we would have done it this way.

For this diagram, we used the actual class/file names and the actual capitalization. The interface is called "IDisplay" with a capital "I" in front of "Display". There were actually programming languages requiring such a naming for interfaces. I am sure there are a few readers still using them and smiling right now.

The problem with this naming scheme is the misplaced concern. Interfaces belong to their clients. Our interface belongs to Game. Thus Game must not know it uses an interface or a real object. Game must not be concerned about the implementation it actually gets. From Game's point of view, it just uses a "Display", that's all.

This solves the Game to Display naming problem. Using the "Impl" suffix for the implementation is somewhat better. It helps eliminate the concern from Game.

It is also much more effective for us. Think of Game as it looks right now. It uses a Display object and knows how to use it. If we name our interface "Display", we will reduce the number of changes needed in Game.

But still, this naming is just marginally better than the previous one. It allows only one implementation for Display and the name of the implementation won't tell us what kind of display we are talking about.

Now that is considerably better. Our implementation was named "CLIDisplay", as it outputs to the CLI. If we want an HTML output or a Windows desktop UI, we can easily add all that to our architecture.

Show Me the Code

As we have two types of tests, the slow golden master and the fast unit tests, we want to rely on unit tests as much as we can, and on golden master as little as we can. So let's mark our golden master tests as skipped and try to rely on our unit tests. They are passing right now and we want to make a change that will keep them passing. But how can we do such a thing, without doing all the changes proposed above?

Is there a way of testing that would allow us to take a smaller step?

Mocking Saves the Day

There is such a way. In testing, there is a concept called "Mocking".

Wikipedia defines Mocking as such, "In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways."

Such an object would be of great help for us. In fact, we don't even need something as complex as simulating all the behavior. All we need is a fake, stupid object that we can send to Game instead of the real display logic.

Creating the Interface

Let's create an interface called Display with all the public methods of the current concrete class.

As you can observe, the old Display.php was renamed to DisplayOld.php. This is just a temporary step, that allows us to take it out of the way and concentrate on the interface.

That's all there is to creating an interface. You can see that it is defined as "interface" and not as a "class". Let's add the methods.

Yes. An interface is just a bunch of function declarations. Imagine it as a C header file. No implementations, just declarations. It can not hold an implementation at all. If you try to implement any of the methods, it will result in an error.

But these very abstract definitions allow us something wonderful. Our Game class now depends on them, instead of a concrete implementation. However, if we try to run our tests, they will fail.

That is because Game tries to create a new display on its own at line 25, in the constructor.

We know we can't do that. An interface or an abstract class can not be instantiated. We need a real object.

Dependency Injection

We need a dummy object to be used in our tests. A simple class, implementing all the methods of the Display interface, but doing nothing. Let's write it directly inside our unit test. If your programming language does not allow several classes in the same file, feel free to create a new file for your dummy class.

As soon as you say your class implements an interface, the IDE will allow you to automatically fill in the missing methods. This makes creating such objects very fast, in just a few seconds.

Now let's use it in Game by initializing it in its constructor.

This makes the test pass, but introduces a huge problem. Game must know about its test. We really don't want this. A test is just another entry point. The DummyDisplay is just another user interface. Our business logic, the Game class, should not depend on the UI. So let's make it depend only on the interface.

But in order to test Game, we need to send in the dummy display from our tests.

That's it. We needed to modify a single line in our unit tests. In the setup, we shall send in, as a parameter, a new instance of DummyDisplay. That is a dependency injection. Using interfaces and dependency injection helps especially if you are working in a team. We at Syneto observed that specifying an interface type for a class and injecting it, will help us communicate much better the intentions of the client code. Anyone looking at the client will know what type of object is used in the parameters. And a cool bonus is that your IDE will autocomplete the methods for those parameters because it can determine their types.

A Real Implementation for Golden Master

The golden master test, runs our code as in the real world. In order to make it pass, we need to transform our old display class into a real implementation of the interface and send it in into our business logic. Here is one way to do it.

Rename it to CLIDisplay and make it implement Display.

In RunnerFunctions.php, in the run() function, create a new display for CLI and pass it to Game when it is created.

Uncomment and run your golden master tests. They will pass.

Final Thoughts

This solution effectively leads to an architecture like in the diagram below.

So now our game runner, which is the entry point to our application, creates a concrete CLIDisplay and thus depends on it. CLIDisplay depends only on the interface which sits on the boundary between presentation and business logic. Our runner also directly depends on the business logic. This is how our application looks like when projected on to the clean architecture that we started this article with.

Thank you for reading, and don't miss the next tutorial when we will talk about mocking and class interaction in more details.

Tags:

Comments

Related Articles