In the sample code, we take a look at an application that is split into layers (each layer is a separate project):
The code from the ViewModel constructor looks like this:
There are several drawbacks to this (as is mentioned in the presentation). One is that due to the tight coupling between the ViewModel and the Repository, it would be difficult to add a different repository type (like one that uses a SQL or CSV data store). We would end up modifying our ViewModel with some sort of conditional to decide what sort of repository to use.
But the real question is whether the ViewModel should even care what kind of repository it's using? The answer is No. To fix this, we added a layer of abstraction (a repository interface) and used Constructor Injection to pass the responsibility of deciding what concrete type is used to someone else. We end up with the following code in our ViewModel:
This is good for our ViewModel: it is no longer tightly coupled to a particular repository; it only knows about an abstraction (the interface). We can remove the project reference to the PersonServiceRepository assembly. If we want to use a CSV repository (that implements IPersonRepository), then the ViewModel does not need to change (and this is what the presentation code actually shows). Success!
But someone has to be responsible for deciding which concrete Repository we're using. The sample code moves this into the UI application (we'll have a bit more explanation of why this was chosen for this sample in just a bit).
The App.xaml.cs has the startup code for our application. Here's where we put the objects together:
What we have here is commonly referred to as the "Composition Root". This is the spot in our application where our objects are assembled -- we're taking our construction blocks and snapping them together. This is where we instantiate our Repository and pass it to the ViewModel. Then we take our instantiated ViewModel and pass it to our View (MainWindow.xaml).
If we want to change to a different Repository, we just update our composition root. We don't need to change our ViewModel or any other code in our application.
The Question: Are We Just Moving the Tight Coupling?
This is where the question came up. We've moved where we are instantiating our objects into the core application project. But now the application needs to have references to the PersonServiceRepository as well as the MainWindowViewModel and everything else in our application. Are we just moving the tight coupling?
The answer for this example is technically "Yes". But that's not necessarily a bad thing (and if we don't like it, there are other solutions as well). Let's take a closer look.
I put together the sample code in order to show the basics of Dependency Injection. This means that I simplified code in several places so that we can better understand the basic concepts. In the "real" applications that I've worked on, we did not have a hard-coded composition root, it was more dynamic (we'll look at this concept in a bit).
But even with the tight coupling in our composition root, we have loose coupling between our components (View, ViewModel, Repository). This means we still get the advantages that we were looking for. If we want to add a new Repository type, we don't need to modify our ViewModel. We do need to modify our composition root, but that's okay because our composition root is actually responsible for snapping the blocks together.
In the full sample, we also see that even with the tight coupling in the composition root, we still get the advantage of extensibility (by easily adding different repositories) and testability (by having "seams" in the code that make it easier to isolate code for unit tests).
But, we have some other options.
When using dependency injection and/or creating modular applications, there's a concept of "bootstrapping" the application. The bootstrapper is where we put the code that the application needs to find everything that it needs.
We could easily add another layer to this application:
This gives us a good place to start looking at the dynamic loading features of our tools. The sample code shows how we can use Unity to explicitly register our dependencies (through code or configuration). But we can also write a little bit of code to scan through a set of assemblies and automatically register the types that it finds there. If you want to pursue this route, you can take a look at Mark Seemann's book (Dependency Injection in .NET) or perhaps look at a larger application framework (such as Prism) that helps with modularization and dynamic loading.
Our sample shows "eager loading" -- that is, we're creating all of our objects up-front (whether we use them immediately or not). This is probably not the best approach for an application of any significant size. Again, the sample is purposefully simple so that we can concentrate on the core DI topics.
But we don't have to create our objects at the beginning. For example, when using Unity, we just need to put our registrations somewhere before we use the objects. The most convenient place is to register the objects that we need in the composition root / bootstrapper.
Registration does not instantiate any of these objects -- it simply puts them in a catalog so that the Unity container knows where to find the concrete types when it needs them. The objects are created when we start to resolve the objects from the container. Our sample container code shows a "Resolve" of the MainWindow right in the composition root. This has the effect of instantiating all of the required objects (the Repository, ViewModel, and View).
But we could resolve the View only when we are about to use it. In that case, none of the objects are instantiated up front. They get loaded as we need them.
This is a great approach, especially if you are doing modular programming. And this is exactly what we did in a recent project that uses Prism. In that project, we had a number of isolated modules (each module contained the Views and ViewModels for a particular function). When a module is created, it registers all of it's types with the Unity container. Then when a particular View is requested (through the Navigation features of Prism), that View is resolved from the container. At that point, the View is instantiated (along with any dependencies).
Lots of Options
There are a lot of options for us to choose from. Ultimately, which direction we go depends on the requirements of our application. If we don't need the flexibility of swapping out components at runtime, then we can use compile-time configuration (which still gives us the advantages of extensibility and testability). If we have a large number of independent functions, then we can look at creating isolated modules that only create objects as they are needed.
Remember that Dependency Injection is really a set of patterns that allows us to write loosely-coupled code. The specific implementation is up to us. There are plenty of examples as well as tools and frameworks to choose from. Take a quick look to see which ones will fit in best with your environment.