使用DI和IoC的工厂方法

Mis*_*tyK 36 c# dependency-injection inversion-of-control factory-pattern

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

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;
            }
     }
}
Run Code Online (Sandbox Code Playgroud)

通常,问题在于需要注入的引用量.当有更多的汽车时会更糟.

我想到的第一种方法是在工厂构造函数中注入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;
            }
     }
}
Run Code Online (Sandbox Code Playgroud)

替代方式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;
            }
     }
}
Run Code Online (Sandbox Code Playgroud)

Nig*_*888 70

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

对于这种情况,最好的,最友好的DI解决方案是策略模式.它允许您的DI容器将依赖项注入它们所属的工厂实例,而不会使具有这些依赖项的其他类混乱或诉诸于服务定位器.

接口

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

public interface ICarStrategy
{
    ICar CreateCar(Type type);
}
Run Code Online (Sandbox Code Playgroud)

工厂

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

战略

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

// 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));
Run Code Online (Sandbox Code Playgroud)

请注意,由于没有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));
Run Code Online (Sandbox Code Playgroud)

  • 说到IoC,抽象工厂模式的一个用途就是从其余的代码中抽象出`new`操作符来隔离它,使它对DI友好.这使得它与使用工厂的代码松散耦合.没有要求您必须返回容器来解析实例 - 事实上,这将比使用容器更快地执行.也就是说,将容器注入抽象工厂是一种常见的技术[允许第三方与框架集成](http://blog.ploeh.dk/2014/05/19/di-friendly-framework/)使用DI . (3认同)
  • 您的CarStrategy并不是真正的策略,而是AbstractFactory。并且您正在通过FirstOrDefault方法进行切换/分支。工厂中的分支代码**不是代码异味**。创建对象时的分支语句是“工厂方法” /“抽象工厂”模式的存在理由。 (3认同)
  • 但是,在IoC方面,是不是使用了被认为是禁止的新运算符?工厂能否以不同的方式实施(不使用新的而不使用服务定位器模式)? (2认同)
  • 对我来说很搞笑的是,当你找到正确的实现时,你对在工厂中分支代码的想法感到厌恶,但却使用 FirstOrDefault 来找到你的......这是完全相同的事情。这里的改进是您通过 DI 接受所有实现,这很好,但除此之外就没有什么了。 (2认同)

Ark*_*z K 9

用你的答案回答你对代码示例的评论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);
 }
Run Code Online (Sandbox Code Playgroud)

这就是你Composition Root使用Unity DI容器的方式:

Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
Run Code Online (Sandbox Code Playgroud)