The actual design pattern is called Inversion of Control (IOC)
and can be implemented in different ways (events,
delegate etc.). DI
is an implementation of IOC
and is working by constructor injection, property injection or method injection.
The main goal of IOC
and DI
is to remove dependencies of classes, this makes the code decoupled and much more maintainable.
Rather than hard code your dependencies directly in the class like this:
public class FooViewModel : BaseViewModel
{
private BarViewModel m_barViewModel;
public FooViewModel()
{
BarViewModel = new BarViewModel();
}
public BarViewModel BarViewModel
{
get => m_barViewModel;
private set => SetProperty(ref m_barViewModel, value);
}
}
One could let someone else inject
the dependency into the class by its constructor, like this:
public class FooViewModel : BaseViewModel, IFooViewModel
{
public FooViewModel(IBarViewModel barViewModel)
{
BarViewModel = barViewModel;
}
private IBarViewModel m_barViewModel;
public IBarViewModel BarViewModel
{
get => m_barViewModel;
private set => SetProperty(ref m_barViewModel, value);
}
}
The biggest change here is that we have created an abstraction for BarViewModel
by adding an interface called
IBarViewModel
.
First of all, working with abstractions rather than implementations is a winning strategy.
You have an application where the user should choose what kind of persistence (Database, local JSON file, In memory) it
would like to use. You could create some kind of configuration that the user can fiddle with, and you could design your
application to read the configuration and inject the correct type of implementation down your application. You would
only need to create an interface called IPersistence
or more popular: IRepository
. Now you have to create a
class for each persistence choice and implement that interface.
This kind of architecture leads to loosely coupled code, that can in fact be much easier to test.
My rule of thumb is; whenever I new
something up I ask myself if I can create a abstraction and pass it into the constructor instead. Because I know that this will serve much better when I move onto testing.
In most cases I rarely switch implementations of an abstraction. But in the rare cases where I have to, I thank myself that I chose this approach.
By using a DI
container. And there is loads of them!.
Now, for this project I have chosen LightInject. This is because this is the one I am most familiar with.
- Add
IFooViewModel
to the constructor ofMainWindow
and set theDataContext
to the instance ofIFooViewModel
. - Remove
StartupUrl="MainWindow.xaml
fromApp.xaml
. - Override
void OnStartup(StartupEventArgs e)
inApp.xaml.cs
with the following:- Call
base.OnStartup(e)
because we have to. - Create a new
LightInject
container, and turns of property injection (because this can be troublesome when working with Wpf). - Register a
CompositionRoot
in the container, this class is where I set up the dependencies for the project. - Next I resolve
MainWindow
from the container and runShow()
. - Resolving means that it has used the container to fetch a
MainWindow
instance with its dependencies injected into it.
- Call
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false });
container.RegisterFrom<CompositionRoot>();
container.GetInstance<MainWindow>().Show();
}
- Create
CompositionRoot
that implementsICompositionRoot
fromLightInject
:- Register
IFooViewModel
,IBarViewModel
, andMainWindow
. The flow of the dependencies is:MainWindow
wantsIFooViewModel
that wantsIBarViewModel
.
- Register
public void Compose(IServiceRegistry serviceRegistry)
{
serviceRegistry.Register<IBarViewModel, BarViewModel>();
serviceRegistry.Register<IFooViewModel, FooViewModel>();
serviceRegistry.Register<MainWindow>();
}
So when resolving MainWindow
, it will resolve an IFooViewModel
that will inject an IBarViewModel
into
FooViewModel
and inject that FooViewModel
into MainWindow
.
The benefit of using DI
is that you can test your classes in total isolation. What this means is that you can test only your class and verify that it uses its dependencies, without going into much details about your dependencies.
You can also use frameworks like Moq
to create mocks / stubs for your dependencies.
I have used Moq
because this is what I am familiar with.
I won't go into much details about test driven development, but I have added a FooViewModelTests
class.
This test class is using DI to its favor by creating a mock / stubbed version of IBarViewModel
. That way I can test FooViewModel
in total isolation. With Moq it can verify that methods on IBarViewModel
has been invoked and setup what methods or properties IBarViewModel
should return.