Factory method with DI and IoC

Having a switch case statement inside of a factory is a code smell. Interestingly, you don’t seem to be focusing on solving that issue at all.

The best, most DI friendly solution for this scenario is the strategy pattern. It allows your DI container to inject the dependencies into the factory instances where they belong, without cluttering up other classes with those dependencies or resorting to a service locator.

Interfaces

public interface ICarFactory
{
    ICar CreateCar();
    bool AppliesTo(Type type);
}

public interface ICarStrategy
{
    ICar CreateCar(Type type);
}

Factories

public class Car1Factory : ICarFactory
{
    private readonly IDep1 dep1;
    private readonly IDep2 dep2;
    private readonly IDep3 dep3;
    
    public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3)
    {
        this.dep1 = dep1 ?? throw new ArgumentNullException(nameof(dep1));
        this.dep2 = dep2 ?? throw new ArgumentNullException(nameof(dep2));
        this.dep3 = dep3 ?? throw new ArgumentNullException(nameof(dep3));
    }
    
    public ICar CreateCar()
    {
        return new Car1(this.dep1, this.dep2, this.dep3);
    }
    
    public bool AppliesTo(Type type)
    {
        return typeof(Car1).Equals(type);
    }
}

public class Car2Factory : ICarFactory
{
    private readonly IDep4 dep4;
    private readonly IDep5 dep5;
    private readonly IDep6 dep6;
    
    public Car2Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
    {
        this.dep4 = dep4 ?? throw new ArgumentNullException(nameof(dep4));
        this.dep5 = dep5 ?? throw new ArgumentNullException(nameof(dep5));
        this.dep6 = dep6 ?? throw new ArgumentNullException(nameof(dep6));
    }
    
    public ICar CreateCar()
    {
        return new Car2(this.dep4, this.dep5, this.dep6);
    }
    
    public bool AppliesTo(Type type)
    {
        return typeof(Car2).Equals(type);
    }
}

Strategy

public class CarStrategy : ICarStrategy
{
    private readonly ICarFactory[] carFactories;

    public CarStrategy(ICarFactory[] carFactories)
    {
        this.carFactories = carFactories ?? throw new ArgumentNullException(nameof(carFactories));
    }
    
    public ICar CreateCar(Type type)
    {
        var carFactory = this.carFactories
            .FirstOrDefault(factory => factory.AppliesTo(type));
            
        if (carFactory == null)
        {
            throw new InvalidOperationException($"{type} not registered");
        }
        
        return carFactory.CreateCar();
    }
}

Usage

// I am showing this in code, but you would normally 
// do this with your DI container in your composition 
// root, and the instance would be created by injecting 
// it somewhere.
var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6)
    });

// And then once it is injected, you would simply do this.
// Note that you could use a magic string or some other 
// data type as the parameter if you prefer.
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));

Note that because there is no switch case statement, you can add additional factories to the strategy without changing the design, and each of those factories can have their own dependencies that are injected by the DI container.

var strategy = new CarStrategy(new ICarFactory[] {
    new Car1Factory(dep1, dep2, dep3),
    new Car2Factory(dep4, dep5, dep6),
    new Car3Factory(dep7, dep8, dep9)
    });
    
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));
var car3 = strategy.CreateCar(typeof(Car3));

Leave a Comment