Let's say I have an interface named ICustomer
that defines one method startProcessing()
:
interface ICustomer
{
void startProcessing(byte[] data);
}
Now, I have multiple concrete classes like ProductA_User
and ProductB_User
. Each one has different properties but they all implement ICustomer
as they are all customers:
class ProductA_User : ICustomer
{
string _name;
IRepository _repo;
public ProductA_User(string name)
{
_name = name;
}
public void startProcessing(byte[] data)
{
Console.WriteLine(_name);
var temp = _repo.Get<VModel>(filter: x.CName == _name).ToList();
}
}
class ProductB_User : ICustomer
{
public void startProcessing(byte[] data)
{
throw new System.NotImplementedException();
}
}
I am invoking a shared method in a factory class named MemberFactory
and creating object based on productType
.
enum ProductTypeEnum { ProductA, ProductB }
class MemberFactory
{
ICustomer Create(ProductTypeEnum productType)
{
switch (productType)
{
case ProductTypeEnum.ProductA: return new ProductA_User("Steve");
case ProductTypeEnum.ProductB: return new ProductB_User();
default: return null;
}
}
}
I give it as param of the enum. Since each concrete class is different but implements ICustomer
, I am able to return an instance that implements ICustomer
. In ProductA_User()
I am trying to inject _repo
which is nothing but EF repo.
what I should do? I don't want to pass extra parameter (_repo
) to create object of class. how can I get any injection done to my concreate class?
CodePudding user response:
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 ofMemberFactory
. - If it's creating anything that needs an instance of
IRepository
, useSomeConcreteRepository
.
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 ofSomeClassThatNeedsMemberFactory
, so I'll create one of those. - Wait, the constructor for
SomeClassThatNeedsMemberFactory
needs an instance ofIMemberFactory
. When I'm asked forIMemberFactory
I useMemberFactory
, so I'll create one of those. - Wait, the constructor for
MemberFactory
needs an instance ofIRepository
. I'll create an instance ofSomeConcreteRepository
.
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.