带有 DI 和 IoC 的工厂方法

Factory method with DI and IoC

我熟悉这些模式,但仍然不知道如何处理以下情况:

public class CarFactory
{
     public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6)
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(Dep1,Dep2,Dep3);
               break;

               case B:
                   return new Car2(Dep4,Dep5,Dep6);
               break;
            }
     }
}

一般来说,问题在于需要注入的引用数量。车多了就更惨了

我想到的第一个方法是在工厂构造函数中注入 Car1 和 Car2,但它反对工厂方法,因为工厂将 return 始终是同一个对象。第二种方法是注入 servicelocator 但它到处都是反模式。如何解决?

编辑:

替代方法 1:

public class CarFactory
{
     public CarFactory(IContainer container)
     {
        _container = container;
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return _container.Resolve<ICar1>();
               break;

               case B:
                     return _container.Resolve<ICar2>();
               break;
            }
     }
}

替代方法2(由于树中的依赖太多而难以使用):

public class CarFactory
{
     public CarFactory()
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....)
               break;

               case B:
                    return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....)
               break;
            }
     }
}

首先,你有一个具体的工厂,一个 IoC 容器可能是一个替代品,而不是帮助你的东西。

然后,只需重构工厂,不要期望工厂构造函数中有完整的可能参数列表。这是主要问题 - 如果工厂方法不需要它们,为什么要传递这么多参数?

我宁愿将特定参数传递给工厂方法

public abstract class CarFactoryParams { }

public class Car1FactoryParams : CarFactoryParams
{
   public Car1FactoryParams(Dep1, Dep2, Dep3) 
   { 
      this.Dep1 = Dep1;
      ...
}

public class Car2FactoryParams 
      ...

public class CarFactory
{
    public ICar CreateCar( CarFactoryParams params )
    {
        if ( params is Car1FactoryParams )
        {
            var cp = (Car1FactoryParams)params;
            return new Car1( cp.Dep1, cp.Dep2, ... );
        }
        ...
        if ( params is ...

通过将参数列表封装在特定的 class 中,您只需让客户端提供特定工厂方法调用所需的这些参数。

编辑:

不幸的是,从您的 post 中并不清楚这些 Dep1 是什么,...以及您如何使用它们。

我建议采用以下方法将工厂提供者与实际工厂实施分开。这种方法被称为 Local Factory 模式:

public class CarFactory
{
   private static Func<type, ICar> _provider;

   public static void SetProvider( Func<type, ICar> provider )
   {
     _provider = provider;
   }

   public ICar CreateCar(type)
   {
     return _provider( type );
   }
}

工厂本身没有任何实现,它在这里为您的域 API 设置基础,您希望仅使用此 API 创建您的汽车实例。

然后,在组合根中(在您配置实际容器的应用程序起点附近的某个位置),配置提供者:

CarFactory.SetProvider(
    type =>
    {
        switch ( type )
        {
           case A:
             return _container.Resolve<ICar1>();
           case B:
             return _container.Resolve<ICar2>();
           ..
    }
);

请注意,工厂提供者的此示例实现使用委托,但接口也可用作实际提供者的规范。

此实现基本上是您编辑的问题中的第一名,但是,它没有任何特别的缺点。客户端仍然调用:

var car = new CarFactory().CreareCar( type );

我会考虑为依赖项提供一个良好的结构,以便您可以使用类似于 Wiktor 的答案的内容,但我会抽象汽车工厂本身。然后,你不使用 if..then 结构。

public interface ICar
{
    string Make { get; set; }
    string ModelNumber { get; set; }
    IBody Body { get; set; }
    //IEngine Engine { get; set; }
    //More aspects...etc.
}

public interface IBody
{
    //IDoor DoorA { get; set; }
    //IDoor DoorB { get; set; }
    //etc
}

//Group the various specs
public interface IBodySpecs
{
    //int NumberOfDoors { get; set; }
    //int NumberOfWindows { get; set; }
    //string Color { get; set; }
}

public interface ICarSpecs
{
    IBodySpecs BodySpecs { get; set; }
    //IEngineSpecs EngineSpecs { get; set; }
    //etc.
}

public interface ICarFactory<TCar, TCarSpecs>
    where TCar : ICar
    where TCarSpecs : ICarSpecs
{
    //Async cause everything non-trivial should be IMHO!
    Task<TCar> CreateCar(TCarSpecs carSpecs);

    //Instead of having dependencies ctor-injected or method-injected
    //Now, you aren't dealing with complex overloads
    IService1 Service1 { get; set; }
    IBuilder1 Builder1 { get; set; }
}

public class BaseCar : ICar
{
    public string Make { get; set; }
    public string ModelNumber { get; set; }
    public IBody Body { get; set; }
    //public IEngine Engine { get; set; }
}

public class Van : BaseCar
{
    public string VanStyle { get; set; } 
    //etc.
}

public interface IVanSpecs : ICarSpecs
{
    string VanStyle { get; set; }
}

public class VanFactory : ICarFactory<Van, IVanSpecs>
{
    //Since you are talking of such a huge number of dependencies,
    //it may behoove you to properly categorize if they are car or 
    //car factory dependencies
    //These are injected in the factory itself
    public IBuilder1 Builder1 { get; set; }
    public IService1 Service1 { get; set; }

    public async Task<Van> CreateCar(IVanSpecs carSpecs)
    {
        var van = new Van()
        {
           //create the actual implementation here.
        };
        //await something or other
        return van;
    }
}

我没有列出来,但你现在可以实现多种类型的汽车及其对应的工厂,并使用 DI 注入任何你需要的东西。

前段时间我回答过类似的问题。基本上这完全取决于您的选择。您必须在冗长(编译器为您提供更多帮助)和自动化之间做出选择,自动化允许您编写更少的代码,但更容易出现错误。

是我支持冗长的回答。

也是支持自动化的好答案

编辑

我相信你认为错误的方法实际上是最好的。说实话,通常那里不会有 so 很多依赖项。我喜欢这种方法,因为它非常明确并且很少导致运行时错误。

Alternative way 1:

这个不好。它实际上是一个服务定位器,被认为是 anti-pattern.

Alternative way 2

就像你写的那样,如果和IOC容器混合使用就不好用了。然而,在某些情况下,类似的方法 (poor man's DI) 可能会有用。

总而言之,我不介意在你们的工厂中有 "many" 依赖项。这是一个简单的声明性代码。编写只需几秒钟,可以节省您与运行时错误斗争的时间。

许多 DI 容器支持命名依赖项的概念。

例如(结构图语法)

For<ICar>().Use<CarA>().Named("aCar");
Container.GetNamedInstance("aCar") // gives you a CarA instance

如果你使用类似约定的东西,规则名称是如何从具体的汽车类型本身派生的,那么当你扩展系统时,你就不需要再接触工厂了。

在工厂中使用它很简单。

class Factory(IContainer c) {
  public ICar GetCar(string name) {
    Return c.GetNamedInstance(name);
  }
}

Composition Root 回答您对代码示例的评论。 您可以创建以下内容,这不是服务定位器。

public class CarFactory
{
    private readonly Func<Type, ICar> carFactory;

    public CarFactory(Func<Type, ICar> carFactory)
    {
       this.carFactory = carFactory;
    }

    public ICar CreateCar(Type carType)
    {
        return carFactory(carType);
 }

这就是你的 Composition Root 使用 Unity DI 容器的样子:

Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));

在工厂内部使用 switch case 语句是一种代码味道。有趣的是,你似乎根本没有专注于解决那个问题。

这种情况下最好、最友好的 DI 解决方案是 strategy pattern。它允许您的 DI 容器将依赖项注入到它们所属的工厂实例中,而不会使其他 类 与这些依赖项混淆或求助于服务定位器。

接口

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

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

工厂

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);
    }
}

策略

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();
    }
}

用法

// 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));

请注意,因为没有 switch case 语句,您可以在不更改设计的情况下向策略添加额外的工厂,并且每个工厂都可以有自己的依赖项,这些依赖项由 DI 容器注入。

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));