A friend of mine recently watched my Dependency Injection On-Ramp course on Pluralsight and sent me a few questions. In my experience, if one person has a question, there are probably other people with the same question. So, here are the questions and answers.
Note: these same questions/answers apply to my live presentation: Dependency Injection: A Practical Introduction.
Interfaces vs. Abstract Classes
Q: I see that interfaces are used with DI. Is there a reason abstract classes aren't used? Just wondering.
In the example, we create a repository interface so that we can add a layer of abstraction. Then we use constructor injection to get the repository into our view model class. Here's that code:
In this case, even though we need a compile time reference to the view model class, we have still freed the view from the responsibility of creating and managing the lifetime of the view model object. This means that creating the view model is not the responsibility of the view (separation of concerns) and the view can concentrate on what it does best -- providing the user interface for the application (it's single responsibility).
I guess I haven't answered the question yet (well, I've sort of answered it).
A: We can use whatever abstractions we like with Dependency Injection.
The type of abstraction is completely separate from dependency injection itself. That means we can use interfaces, abstract classes, or base classes. To make the decision, we'll just look at the pros and cons of using these types just like we do in our other code.
Specifically, interfaces are good where we do not have any shared code in the implementations (thus, no duplication). We can implement as many interfaces as we want, so we are not limited by single inheritance when we apply interfaces. And we're free to descend from another class if we want.
Abstract classes are good where we have shared code in the implementations. This lets us put the shared code into the abstract class and all of the child classes can use it (thus, no duplication). We are limited by single inheritance, so we cannot descend from an abstract class and another class at the same time.
If you want more information on how to make a decision between interfaces and abstract classes, you can check out my C# Interfaces course on Pluralsight, or reference the materials for my live presentation: IEnumerable, ISaveable, IDontGetIt: Understanding .NET Interfaces.
Client Applications vs. Server Applications
Q: All of your examples are with a client app. Can this be used server side, too? I'm assuming yes.
In the examples, we use Dependency Injection to break the coupling between the layers in a WPF (desktop client) application. Specifically, we break the coupling between our view model (presentation) and repository (data access) so that we can more easily extend and test our application.
And we can get these same benefits server-side as well. So, the answer is...
A: Yes, of course.
Another of my presentations uses an ASP.NET MVC project as an example: Shields Up! Defensive Coding in C#.
This example has a custom controller factory that uses Ninject. Here's that code:
For this particular example, I'm showing the dangers of SQL injection. So I have a SQL repository that is vulnerable to SQL injection and another repository that uses parameterized SQL (and thus, is not vulnerable to this specific attack). Using the IRepository abstraction along with Ninject allows me to swap this out very quickly in my demo.
Okay, so you're probably saying that isn't a "real" server application (although, the code is running on the IIS server, not the client machine). So, here's another example. This is taken from a lab for an upcoming Education Day (more info on this soon).
This is an application that is designed to run inside an automated job. It loads a file, parses the data, and outputs any errors to a log file. In this case, we want to isolate the data parsing code to make it easier to unit test. So instead of having a logger hard-coded in the parser, we use constructor injection:
So we can get the benefits from Dependency Injection (extensibility, testability, maintainability, late binding, parallel development) whether we are working with client applications or server-side code.
More Advanced Ninject
Q: How would you use the CachingRepository decorator with a container such as Ninject?
In the example code, we start out pretty simply: we manually wire our application pieces together in the composition root of our application. Here's the example using the repository that uses a WCF SOAP service:
Using a container eliminates some of these steps. Rather than wiring everything together, we just create a binding between the abstraction (IPersonRepository) and the concrete type (PersonServiceRepository):
But in the example, we also created this really cool caching repository. This wraps any existing repository and adds a client-side cache to it. This was pretty easy to wire up manually:
But what about using a container? We can't just associate the IPersonRepository with the CachingPersonRepository because it also needs an IPersonRepository as a parameter. It seems like we'd end up in an endless loop.
A: Ninject (and other DI containers) provide lots of advanced features to handle complex situations.
When we configure our container, we just need to supply a little more information:
This has the same effect as our manual code: it takes a PersonServiceRepository and wraps it in the caching repository.
What this shows is that it pays to spend some time with a particular DI container. Learn the ins-and-outs of what is available. There are tons of options.
Dependency Injection is still one of my favorite topics. It's a powerful tool that can give us some good advantages in many situations. Like every tool, it is not appropriate everywhere. But if we understand what it is good for, then we can make sure we use it in the right situations.
I'm always open to answering questions about the topics that I speak about. There is no such thing as a stupid question. This is how we learn.