Dependency Inversion With Clojure

This week my focus was to update my project to ensure I was following SOLID design principles. Now as a novice to both functional programming and Clojure, making sure my code adhered to the SOLID principles was a difficult task. Most programing examples are written in object oriented languages like Java. Secondly Clojure’s interface like features have their own domain specific names and means of implementation.

One of the more interesting principles to maintain in Clojure is Dependency Inversion. Once you get your head around it, Dependency Inversion in Clojure is pretty simple. First lets review the Dependency Inversion principle.

Dependency Inversion Principle

1. High level modules should not depend on low level modules. Both should depend on abstractions.

2. Abstractions should depend on details, not details on abstractions.

So how does this work in the wild? Well, let’s first look at a dependency violation.

If you take a look at this example you see I have a method call game-runner that gets moves from both a human player using a function from the Human name space called user-move and a computer move from the ai name space called ai-move (lines 11 and 13).

In this example my core namespace are directly dependent on the ai and human namespace to process player moves. This is a violation of the dependency inversion principle.

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

To overcome this we need to create a player protocol called player that the ai and human name spaces implement. See the example below, The protocol says any implementation of the player protocol must have a next-move function. It does not contain details and thus depends on the implementation to provide details.

Abstractions should depend on details, not details on abstractions.

Now in our Human namespace we can implement the player protocol using what we call in Clojure a defrecord. We supply the defrecord a marker and we provide the details for the required next-move function. In this example we pass the human-move function.

Now back to our core namespace. We removed the dependencies on the human and ai namespace and replace it with the player protocol’s next-move function. Now we can inject any type of player we want into the game and the core doesn’t have to be dependent on those player’s(ai/human) details.

So in review, the dependency inversion principle says two things. 1.High level modules should not depend on low level modules.Both should depend on abstractions. 2. Abstractions should depend on details, not details on abstractions. In order to accomplish this in Clojure we need to create protocols and have our namespace implement them via defrecords.

Want to learn more about Abstractions in Clojure ? Checkout Clojure for the brave