繁体   English   中英

使用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,但是这与工厂方法相反,因为工厂将始终返回相同的对象。 第二种方法是注入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;
            }
     }
}

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

针对这种情况的最佳,最直接的DI解决方案是策略模式 它允许您的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)
    {
        if (dep1 == null)
            throw new ArgumentNullException("dep1");
        if (dep2 == null)
            throw new ArgumentNullException("dep2");
        if (dep3 == null)
            throw new ArgumentNullException("dep3");

        this.dep1 = dep1;
        this.dep2 = dep2;
        this.dep3 = 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 Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
    {
        if (dep4 == null)
            throw new ArgumentNullException("dep4");
        if (dep5 == null)
            throw new ArgumentNullException("dep5");
        if (dep6 == null)
            throw new ArgumentNullException("dep6");

        this.dep4 = dep4;
        this.dep5 = dep5;
        this.dep6 = 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)
    {
        if (carFactories == null)
            throw new ArgumentNullException("carFactories");

        this.carFactories = carFactories;
    }

    public ICar CreateCar(Type type)
    {
        var carFactory = this.carFactories
            .FirstOrDefault(factory => factory.AppliesTo(type));

        if (carFactory == null)
        {
            throw new Exception("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));

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

这是使用Unity DI容器的“ Composition Root ”的外观:

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

我前段时间回答了类似的问题。 基本上,一切都取决于您的选择。 您必须在详细程度(这可以为您提供来自编译器的更多帮助)和自动化之间进行选择,它可以使您编写更少的代码,但更容易出现错误。

是我支持冗长的答案。

也是支持自动化的一个很好的答案。

编辑

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

替代方法1:

这个不好。 它实际上是一个服务定位器,被认为是反模式

替代方法2

如您所写,将它与IOC容器混合使用并不容易。 但是,在某些情况下,类似的方法( 穷人的DI )可能会有用。

总而言之,我不会在您的工厂中拥有“许多”依赖项。 这是一个简单的声明性代码。 它花费几秒钟来编写,并且可以节省数小时的运行时错误。

首先,您有一个具体的工厂,一个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 ...

通过将参数列表封装在特定的类中,您只需使客户端确切地提供特定工厂方法调用所需的这些参数即可。

编辑:

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

我建议采用以下方法,然后将工厂提供者与实际工厂实施区分开来。 这种方法称为本地工厂模式:

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

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

从您编辑的问题来看,此实现基本上是#1,但是,它没有任何特殊的缺点。 客户仍然致电:

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

我会考虑给依赖项提供一个良好的结构,以便您可以利用类似于Wiktor的答案的方法,但是我会抽象化Car工厂本身。 然后,您不使用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注入所需的任何东西。

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

例如(Structuremap语法)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM