If you’ve never done Test Driven Development or aren’t even sure what this "crazy TDD stuff” is all about than this is the series for you. Over the next 30 days this series of posts take you from “I can spell TDD” to being able to consider yourself a “functional” TDD developer. Of course TDD is a very deep topic and truly mastering it will take quite a bit of time, but the rewards are well worth it. Along the way I’ll be showing you how tools like JustCode and JustMock can help you in your practice of TDD.
In the previous post I showed you how by starting to leverage the SOLID principles you can unlock the practice of Dependency Injection (DI). I also demonstrated how DI can help you write code that is free of static bindings, which makes your code flexible, open to change and testable. In this post I’ll demonstrate some practical techniques to manage the dependencies in your application by using software factories and DI frameworks.
Previous Posts in this Series Day Six – What is Dependency Injection?
DI gave us a technique to create an application of loosely bound dependencies. By passing the components that our class is dependent on as constructor arguments we can accomplish two things. First we can make our class dependent on abstractions and not concrete instances. Secondly we can defer the decision of which concrete instances are used until runtime.
This does present us with a new problem however. When we instantiate a new instance of a class, the code instantiating that class has to create the components that the instance of our class will be bound to. This reintroduces our tightly coupled static binding; all we’ve done is move the instantiation of a statically bound component up a level. We haven’t solved the problem, we’ve moved the problem.
In addition to moving the original problem, we’ve created a new one. We’ve not only moved the responsibility for creating the components our child classes are dependent on to their parents, we’ve also put those parents in a position where they need to know which concrete classes to supply to the child class they now are responsible for creating. By doing this we’ve now violated another SOILD principle; the Single Responsibility Principle (SRP). The parent class should be responsible for whatever its primary function is and should only change if something about that primary function changes. Now it has to change if some aspect of its child class changes and effect’s its dependencies.
One solution to this problem is the use of a software factory. A software factory is a pattern used to abstract away the necessary knowledge and code needed to create instance of classes. There are several types of Software Factory patterns, each designed to address different situations and needs. For a comprehensive listing of the most common and popular factories, I recommend the book Design Patterns, which provides and in-depth analysis of the various patterns.
While there are several different types of factory patters, they all essentially boil down to the same idea. I have a component that consolidates each class’s dependencies and how those dependencies get created. An example of a simple factory class would be something like this:
1: public class BusinessServiceFactory
3: public static BusinessService GetNewBusinessService()
5: var dataStoreProvider = GetNewDataStoreProvider();
6: var loggingProvider = GetNewLoggingProvider();
7: var webServiceProvider = GetNewWebServiceProvider();
9: return new BusinessService(dataStoreProvider, loggingProvider, webServiceProvider);
12: private static IWebServiceProvider GetNewWebServiceProvider()
14: return new WebServiceProvider();
17: private static ILoggingProvider GetNewLoggingProvider()
19: return new LoggingProvider();
22: private static IDataStoreProvider GetNewDataStoreProvider()
24: return new DataStoreProvider();
Software factories are effective. As you can see here I’ve managed to encapsulate all the functionality needed to create an instance of a new BusinessService class in one place. That means that the classes that consume BusinessService can stick to doing what they’re supposed to be doing; not worrying about what the dependencies of BusinessService are.
This is a simple example of a software factory in which each interface has a one to one relationship with a concrete implementation. But I don’t have to have this kind of relationship. As a factory is simply code, I can use any logic I want to determine specifically what implementation of an interface gets returned. Maybe I want my users to be able to choose between MS SQL Server and Oracle? I can have my factory read a configuration file and determine which concrete class to return. Perhaps I want activities from a specific department logged in a different place than other users. If I’m using Active Directory I can look at the application context and determine which logging provider gets returned based on the current user’s metadata. I can essentially do anything I want.
While software factories are effective, they are not necessarily ideal. While specific types of factory patterns are designed to address specific needs, they all require a bit of maintenance, even in smaller applications. And while the code to build a factory is easy to implement, it’s repetitive and prone to mistakes. Luckily, there is an even better way to manage dependencies in your applications than software factories
Dependency Injection frameworks work similar to software factories but provide a benefit by automating a lot of the tasks that we would normally have to perform manually. For starters, instead of defining an entire method to bind a concrete type to an interface, even if it’s a simple one to one binding, I can define a “binding rule” with one line of code. There are also ways to have most frameworks derive the binding for you, provided you’ve followed a strict, yet simple convention (the convention varies with the specific framework).
There are several popular DI frameworks and they all have a relatively comparable feature set. I will be using Ninject in this series because that is the one I happen to know and use. I’ll provided links to a couple more popular DI frameworks at the end of this article. If you’ve never used a DI framework I recommend trying them all and then picking the one you like the best. They are all good and you can’t really go wrong with any of them.
While each DI framework will have different terms for their components, they all boil down to four essential moving parts:
Please note that in the above list I am using the Ninject terms. The specific names for your DI framework of choice may be different.
To rewrite the above example using Ninject I first need to get a reference to the Ninject library. To do that I could either download Ninject from the Ninject website and reference the ninject.dll assembly manually, or I could use NuGet to download and bind the ninject.dll assembly automatically.
For Ninject the first thing I need to do is define a Module. This is where I define the binding rules for my application:
1: public class DomainModule : NinjectModule
3: public override void Load()
To create a module I need to create a class and have it inherit from NinjectModule. This class is in the Ninject.Module namespace, but if you’re using JustCode it will put this, as well as all namespaces you’ve referenced classes from into your source file. NinjectModule has an abstract method called Load that you will need to override. This is where you will put the binding rules for your classes.
To define my binding rules I use Ninject’s Bind method. In the example above I’m binding the DataStoreProvider class to the IDataStoreProvider interface on line five. This means that whenever Ninject is asked for an instance of the IDataStoreProvider it is going to provide an instance of the DataStoreProvider class.
The binding rules show here are the simplest types of binding rules. Ninject has a complete fluent language that enables you to create more complex, context sensitive binding. In future posts I’ll be showing you some more complex Ninject bindings. For now you can find a pretty comprehensive cheat sheet here.
The binding rules are defined. So, now we need to ask Ninject for an instance of our class. This pretty easy as well, as shown in this example:
1: public class BusinessServiceConsumer
3: private BusinessService _businessService;
5: public BusinessServiceConsumer()
7: var kernel = new StandardKernel(new DomainModule());
9: _businessService = kernel.Get<BusinessService>();
In the code above I am creating an instance of the Ninject StandardKernel class. The StandardKernel takes an instance (or several instances) of a NinjectModule as a constructor argument. The kernel then becomes our container, providing instances of classes when we call the Get<T> method. As you can see above, I am calling the Get method (line 9) to get an instance of my BusinessService. But if you’ll recall from the previous listing, I didn’t define specifically how to create BusinessService. This is because Ninject is able to analyze the BusinessService class and determine what its dependencies (via the constructor argument) are and supply them.
But wait, there’s more! Let’s make a slight change to the DataStoreProvider:
1: public class DataStoreProvider : IDataStoreProvider
3: private ILoggingProvider _loggingProvider;
5: public DataStoreProvider(ILoggingProvider loggingProvider)
7: this._loggingProvider = loggingProvider;
I’ve added a dependency to the ILoggingProvider interface to the DataStoreProvider. Now my BusinessService class has a dependency on a class that also has a dependency. I was able to make a pretty significant change to my application and unlike a software factory, Ninject does not require any additional code. I’ve already specified how to create the LoggingProvider, so Ninject already knows how to create this new dependency. I do not need to do anything else!
I’m just scratching the surface of the functionality of Ninject and I’ll be covering some more advanced features and best practices in a future post. In the meantime these basics I’ve provided should be enough to get you started in your use of DI frameworks.
DI is a powerful technique that enables developers to create loosely coupled software. But the concept of DI in and of itself is not enough; without some thought to how these objects actually get created we’re just trading one problem for another. The use of Software Factories and DI Frameworks such as Ninject will help you by providing and encapsulating creational patterns in efficient and manageable structures.
Continue the TDD journey:
Subscribe to be the first to get our expert-written articles and tutorials for developers!