I was speaking with a colleague about the importance of reducing dependencies. I’ll reproduce here some work that we did to streamline some code and make it easier to test and maintain.
Consider this snippet as our starting point.
public Employee[] GetEmployees() {
var url = MyApplication.Settings.RemoteApi.Url;
var auth = new Auth(MyApplication.User);
var remoteApi = new RemoteApi(auth, url);
var rawData = remoteApi.FindEmployees();
var mapper = new EmployeeMapper();
var mappedResults= mapper.map(rawData);
return mappedResults;
}
Dependencies
MyApplication
MyApplication.Settings
MyApplication.Settings.RemoteApi
MyApplication.Settings.RemoteApi.Url
Auth
User
RemoteApi
FindEmployees
EmployeeMapper
That’s a lot objects or properties that will impact this code if they change for any reason. Further, when we attempt to write an automated test against this code we are dependent on a live existence of an API.
Law of Demeter
We can apply the Law of Demeter to reduce some of the dependencies in this code.
- Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
- Each unit should only talk to its friends; don’t talk to strangers.
- Only talk to your immediate friends.
In practice this usually means replacing code that walks a dependency hiearchy with a well-named function on the hierarchy root and call the function instead. This allows you to change the underlying hierarchy without impacting the code that depends on the function.
Consider the following modifications.
public Employee[] GetEmployees() {
var url = MyApplication.GetRemoteApiUrl();
var auth = MyApplication.GetAuth();
var remoteApi = new RemoteApi(auth, url);
var rawData = remoteApi.FindEmployees();
var mapper = new EmployeeMapper();
var mappedResults= mapper.map(rawData);
return mappedResults;
}
Dependencies
MyApplication
Auth
User
RemoteApi
FindEmployees
EmployeeMapper
I think we can do better.
public Employee[] GetEmployees() {
var remoteApi = MyApplication.GetRemoteApi();
var rawData = remoteApi.FindEmployees();
var mapper = new EmployeeMapper();
var mappedResults= mapper.map(rawData);
return mappedResults;
}
Dependencies
MyApplication
RemoteApi
FindEmployees
EmployeeMapper
This is starting to look good. We’ve reduced the number of dependencies in this function by half by implementing a simple design change.
Dependency Inversion
We still have the problem that we are dependent on a live instance of the API when we write automated tests for this function. We can address this by applying the Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
In practice this means that our GetEmployees
function should not depend on a concrete object. We can address this issue by passing the RemoteApi
to the constructor of the class as opposed to having the method get the RemoteApi
instance through MyApplication
.
public class EmployeeRepository {
private RemoteApi remoteApi;
public EmployeeRepository(RemoteApi remoteApi) {
this.remoteApi = remoteApi;
}
// snipped
public Employee[] GetEmployees() {
var rawData = this.remoteApi.FindEmployees();
var mapper = new EmployeeMapper();
var mappedResults= mapper.map(rawData);
return mappedResults;
}
Dependencies
RemoteApi
FindEmployees
EmployeeMapper
With this change we have reduced dependencies even further. However, we have not satisifed the Dependency Inversion Principle yet because we are still dependent on a concrete class: RemoteApi
. If we extract an interface from RemoteApi
and instead depend on that then the DIP is satisfied.
public interface IRemoteApi {
RawEmployeeData[] FindEmployees();
}
public class EmployeeRepository {
private IRemoteApi remoteApi;
public EmployeeRepository(IRemoteApi remoteApi) {
this.remoteApi = remoteApi;
}
// snipped
public Employee[] GetEmployees() {
var rawData = this.remoteApi.FindEmployees();
var mapper = new EmployeeMapper();
var mappedResults= mapper.map(rawData);
return mappedResults;
}
Dependencies
IRemoteApi
FindEmployees
EmployeeMapper
Now that we are dependent on an abstraction, we can provide an alternative implementation of that abstraction to our automated tests (a test double) that verify the behavior of GetEmployees
. This is a good place to be. Our code is loosely coupled, easily testable, and does not easily break when implementation details in other objects change.
Motivation
I’ve seen several different approaches to Dependency Injection, each of which have their own strengths and weaknesses. I run an internship program in which I teach these patterns to college students. I believe each pattern and anti-pattern has its pros and cons, but my observation is that even experienced devs haven’t fully articulated the cost-benefit of each approach to themselves. This is my attempt to do that.
Property Injection
“Property Injection” or “Setter Injection” refers to the process of assigning dependencies to an object through a Property or Setter method.
Example
<br />public class Widget
{
public Bar Bar {get; set; }
public void Foo(string someValue)
{
Bar.SomeMethod(someValue);
}
}
There are pros and cons to this approach.
Pros
- Enables easy faking for test purposes.
- Keeps method signatures tidy.
- Dependencies are clearly specified if you are looking at the class.
Cons
- Temporal Dependency
What is a temporal dependency? Perhaps it’s best to illustrate with an example. Here is my first attempt at using the class as defined above.
var widget = new Widget();
widget.Foo("Hello World!");
Did you spot the problem? I never set Bar
. When I attempt to call Foo
I’ll get the oh-so-helpful NullReferenceException
. I find out after I try to use the class that I have a missing dependency. I have to open the class to find out which dependency is missing. Then I have to modify my code as follows:
var widget = new Widget();
widget.Bar = new Bar();
widget.Foo("Hello World!");
It’s called a temporal dependency because I have to set it before I can call any methods on the class. What’s worse, the API of the class doesn’t give me any indication that anything is wrong until after I attempt to run it.
Method Injection
“Method Injection” refers to passing dependencies to the method that uses them.
Example
<br />public class Widget
{
public void Foo(Bar bar, string someValue)
{
// snipped
}
}
Pros
- No temporal dependencies
- dependencies are clearly communicated via the API
- Easily replace dependencies with fakes for testing purposes
Cons
- Method signature explosion
- Method signature fragility
- Clients have to concern themselves with the classes dependencies
What are method signature explosion and fragility? Method Signature Explosion means that arguments to my method signatures will increase as dependencies change. This leads to Method Signature Fragility which means that as dependencies change, clients of the method have to change as well. In other words, we lose the benefit of encapsulated logic.
Constructor Injection
Constructor Injection is the process of making dependencies available to a class through its constructor.
Example
<br />public class Widget
{
private Bar _bar;
public Widget(Bar bar)
{
_bar = bar;
}
public void Foo(string someValue)
{
_bar.SomeMethod(someValue);
}
}
Pros
- Enables easy faking for test purposes.
- Keeps method signatures tidy.
- Dependencies are clearly specified through the API
- No temporal dependencies
- No Method Signature Explosion
- No Method Signature Fragility
Cons
- none – other than those inherent to the nature of using Dependency Injection in the first place.
Of the three approaches listed so far, I strongly prefer Constructor Injection. I see nothing but benefits in this approach.
Lazy Injection
If you’re getting started with Dependency Injection, I strongly recommend researching a Dependency Injection Framework such as Ninject to make constructing your class hierarchies easy. If you’re not ready to bite that off you might consider using Lazy Injection. This is a technique by which your constructor arguments are given default values so that you can instantiate your class with all of your default system values at run-time, but pass fakes during test-time.
Example
<br />public class Widget
{
private Bar _bar;
public Widget(Bar bar = new Bar())
{
_bar = bar;
}
public void Foo(string someValue)
{
_bar.SomeMethod(someValue);
}
}
You can do this with Property Injection as well, mitigating some of the cons of that approach. You are still left opening the class to figure out how and what to fake however.
Example
<br />public class Widget
{
public Bar Bar {get; set; }
public class Widget()
{
Bar = new Bar();
}
public void Foo(string someValue)
{
Bar.SomeMethod(someValue);
}
}
Service Locator
Service Locator is widely considered to be an anti-pattern. To understand why, read “ServiceLocator is an Anti-Pattern“.
Service Locator involves making an open-ended registry of dependencies widely available to any class that wants them.
Example
<br />public class Widget
{
public Bar Bar {get; private set; }
public class Widget()
{
Bar = ServiceLocator.Get<Bar>();
}
public void Foo(string someValue)
{
Bar.SomeMethod(someValue);
}
}
On the surface this looks awesome.
Pros
- Keeps method signatures tidy.
- No temporal dependencies
- No Method Signature Explosion
- No Method Signature Fragility
Cons
- Dependencies are not clearly specified through the API
- Because my API doesn’t communicate my dependencies, I have to understand the classes’ implementation details to properly test it.
- It encourages dependency explosion inside the class. This is another way of saying that a class with too many constructor arguments is a “smell” and I lose the benefit of being confronted with that “smell” if I use ServiceLocator.
Despite these flaws, it is sometimes useful as a scaffolding mechanism to introduce a Dependency Injection Framework into an application that was not designed with Dependency Injection in mind. I want to stress that I believe this pattern should only be used as an interim step on the road to full Dependency Injection Framework support in legacy applications. You should make it a point to remove ServiceLocator as quickly as possible after introducing it.
Closing Thoughts
These patterns are by no means exhaustive, but they are the common one’s I’ve seen over the years.
If you are using a Dependency Injection Framework (my favorite is Ninject), some of the cons of these approaches may be mitigated by the Framework. This may change the equation with respect to which method is appropriate for your use-case.
I recently had a subtle production bug introduced by creating more than one Ninject binding for a given interface to the same instance.
I wanted to be able to see what bindings existed for a given interface, but Ninject does not provide an easy way to do that.
This gist contains an extension method I wrote (with the help of a StackOverflow article) to acquire this information.
As this code relies on using reflection to get a private member variable, this code is brittle in the face of a change in the implementation of KernelBase. In the meantime, it works on my machine.
I have created an open-source project on CodePlex called “Yodelay”. From the project description:
The Yodelay .NET Framework Extensions project provides a library of components that make many kinds of programming tasks simpler. These include basic MVVM components, Unit Test Extensions, and a poor-man’s Dependency Injection library.
Most of the classes and extension methods in this library are rei-mplementations or adaptations of components I have written in other places. Some of them have been found in various forms on developer blogs. Where this is true, I have indicated where I retrieved the original source code in the comments. This library is not just a collection of random classes. The attempt here is to bring all of these components together and use them in an integrated fashion to develop stable applications faster.
Not all code in this project is currently covered by unit tests. This is because many of the classes were adapted from blogs or other existing projects and may not have been developed using TDD. Every effort will be made to bring existing classes under test coverage, and to practice TDD when adding new code.
Why did I call it Yodelay? For no other reason than I like the way the word sounds. It rolls off the tongue easily, which is the effect I hope the library has on applications.
There is no installer yet. Yodelay is in version 0.1, but there are already quite a few goodies in the library. Everything is built against the 4.0 framework. I will try to work on it a little each week, adding new components and demo projects when I can. In the near future I will be signing the assemblies and building an installer for them.
Enjoy!