Somewhere you have a class that needs an ICustomer. You don't want that class to know anything about the implementation of ICustomer, so you're using MemberFactory.
But if the class that needs ICustomer has to create MemberFactory and pass arguments to it like an IRepository, then you're back to the same problem again. The class that needs ICustomer shouldn't know that some implemenations of ICustomer need an IRepository.
There are a lot of approaches to this. I'll go with a simple and direct one.
First, create an interface for MemberFactory:
interface IMemberFactory
{
ICustomer Create(ProductTypeEnum productType)
}
Change the declaration of MemberFactory so that it implements the interface:
class MemberFactory : IMemberFactory
Wherever you were injecting MemberFactory, inject IMemberFactory instead.
Now the class that depends on IMemberFactory won't know anything about its implementation. It just knows that it has an IMemberFactory and calls its Create method.
Then, inject the IRepository into the implementation, MemberFactory.
class MemberFactory
{
private readonly IRepository _repository;
public MemberFactory(IRepository repository)
{
_repository = repository;
}
ICustomer Create(ProductTypeEnum productType)
{
switch (productType)
{
case ProductTypeEnum.ProductA: return new ProductA_User("Steve");
case ProductTypeEnum.ProductB: return new ProductB_User();
default: return null;
}
}
}
Now if MemberFactory needs an IRepository to create certain types of ICustomer, it has one. This is not perfect. It could get out of hand if we added more and more dependencies this way. But it's a start.
Then, whatever type needs the IMemberFactory to create ICustomer, inject the IMemberFactory into that:
class SomeClassThatNeedsMemberFactory
{
private readonly IMemberFactory _memberFactory;
public SomeClassThatNeedsMemberFactory(IMemberFactory memberFactory)
{
_memberFactory = memberFactory;
}
}
Now each class has its dependencies injected into it. As a result, a class isn't responsible for creating its own dependencies. That means it doesn't know anything about the concrete implementation of the dependencies. SomeClassThatNeedsMemberFactory doesn't know that the concrete implementation might need an IRepository. All it knows about IMemberFactory is the interface.
That's a big part of dependency injection. Now if we change the implementation of IMemberFactory, its consumers don't need to know about it. They don't know what gets passed to its constructor.
It also makes each class easier to test. If you're testing SomeClassThatNeedsMemberFactory, you can mock IRespository.
I haven't mentioned dependency injection/IoC containers yet because you didn't ask about them, and because they aren't actually central to the concept of dependency injection. Dependency injection is about constructing classes or methods so that they receive dependencies instead of creating them - all the stuff above. A container just helps us to manage all that.
Suppose we're working with IServiceCollection/IServiceProvider which is sort of the default container these days for .NET applications.
We can do this if we're working in the Startup class of a web application, or something similar for other application types.
void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SomeClassThatNeedsMemberFactory>();
services.AddScoped<IMemberFactory, MemberFactory>();
services.AddScoped<IRepository, SomeConcreteRepository>();
}
This tells the service collection
- We're going to ask it to create instances of
SomeClassThatNeedsMemberFactory.
- If it's creating anything that needs an instance of
IMemberFactory, create an instance of MemberFactory.
- If it's creating anything that needs an instance of
IRepository, use SomeConcreteRepository.
Now suppose the application needs to create an instance of a Web API controller, and we've injected SomeClassThatNeedsMemberFactory of that controller. The service provider (dependency injection/IoC container) will say
- I need to create an instance of
FooController. The constructor says that it needs an instance of SomeClassThatNeedsMemberFactory, so I'll create one of those.
- Wait, the constructor for
SomeClassThatNeedsMemberFactory needs an instance of IMemberFactory. When I'm asked for IMemberFactory I use MemberFactory, so I'll create one of those.
- Wait, the constructor for
MemberFactory needs an instance of IRepository. I'll create an instance of SomeConcreteRepository.
As long as we've told it what types to use, it can create complex objects where the dependencies have dependencies that need more dependencies, and so on. But we don't have to think so hard about that. We can just look at one class at a time and pay attention to what dependencies it needs as we're writing it, working on it, or testing it.
That's why containers are so popular. We could do all this without them, but it's harder. They are not, however, what dependency injection is all about. They just enable it.