Home > Software design >  How to supply dependencies to classes that are created by a factory class?
How to supply dependencies to classes that are created by a factory class?

Time:07-26

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 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.

  • Related