I always saw people always talking about using framework like Ninject, Unity, Windsor to do the dependency resolver and injection. Take following code for example:
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
}
My question is: why can't we simply write as:
public class ProductsController : ApiController
{
private IProductRepository _repository;
public ProductsController() :this(null)
{}
public ProductsController(IProductRepository repository)
{
_repository = repository?? new ProductRepository();
}
}
In that case seems we don't need any framework, even for the unit test we can easily mock.
So what's the real purpose for those framework?
Thanks in advance!
We should use the dependency framework because of the following: It helps us in managing the complex dependencies easily. It makes the unit testing easy by enabling us to pass all the dependencies from outside so that we can easily use the mocked objects. It easily manages the scope(lifecycle) of the object.
. NET supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. Dependency injection in . NET is a built-in part of the framework, along with configuration, logging, and the options pattern.
In software engineering, dependency injection is a design pattern in which an object or function receives other objects or functions that it depends on.
A dependency resolver is just a service locator integrated with the ASP.NET MVC codebase. Resolvers are a way to add the implementation of the Dependency Inversion principle into an existing (large) codebase.
In that case your ProductsController
still depends on a low level component (the concrete ProductRepository
in your case) which is a violation of the Dependency Inversion Principle. Whether or not this is a problem depends on multiple factors, but it causes the following problems:
ProductRepository
is still duplicated throughout the application causing you to make sweeping changes throughout the application when the constructor of ProductRepository
chances (assuming that ProductRepository
is used in more places, which is quite reasonable) which would be an Open/Closed Principle violation.ProductService
with a decorator or interceptor that adds cross-cutting concerns (such as logging, audit trailing, security filtering, etc) that you surely don't want to repeat that code throughout all your repositories (again an OCP violation).ProductsController
to know about the ProductsRepository
, which might be a problem depending on the size and complexity of the application your are writing.So this is not about the use of frameworks, it's about applying software design principles. If you decide to adhere to these principles to make your application more maintainable, the frameworks like Ninject, Autofac and Simple Injector can help you with making the startup path of your application more maintainable. But nothing is preventing you from applying these principles without the use of any tool or library.
Small disclaimer: I'm an avid Unity user, and here are my 2 cents.
Already stated by @democodemonkey and @thumbmunkeys, you couple the 2 classes tightly. Let's say that some classes (let it be ProductsThingamajigOne and ProductsThingamajigTwo) are using the ProductsController, and are using its default constructor. What if in the architect decides that the system should not use a ProductsRepository that saves Products into files, but should use a database or a cloud storage. What would the impact be on the classes?
If the repository is based on a database, you might need to provide it with a ConnectionString. If it's based on files, you might need to provide it with a class of settings providing the exact path of where to save the files - and the truth is, that in general, applications tend to contain dependency trees (A dependent on B and C, B dependent on D, C dependent on E, D dependent on F and G and so on) that have more then 2 levels, so the SOLID violations hurts more, as more code has to be changed to perform some task - but even before that, can you imagine the code that would create the whole app? Fact is, classes can have many dependencies of theirs own - and in this case, the issues described earlier multiply.
That's usually the job of the Bootstrapper - it defines the dependency structure, and performs (usually) a single resolve that brings the whole system up, like a puppet on a string.
Consider the following case: Class A dependent on classes B and C, B and C both are dependent on class D, and are expecting to use the same instance of D. A common practice was to make D a singleton, but that could cause a lot of issues. The other option is to pass an instance of D into the constructor of A, and have it create B and C, or pass instances of B and C to A and create them outside - and the complexity goes on and on.
Your code assumes that 'ProductsController' can see 'ProductRepository' (assembly-wise). What if there's no reference between them? the Assembly Map can be non-trivial. usually, the bootstrapping code (I'm assuming that it's in code and not in configuration file for a second here) is written in an assembly that references the entire solution. (This was also described by @Steven).
Singletons are made easy (with unity: simply use a 'containercontrolledlifetimemanager' when registering), Lazy Instantiation made really easy (with unity: register mapping of and ask in the constructor for a Func). Those are just a couple of examples of things that IoC containers give you for (almost) free.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With