24

如果我有以下代码:

public class RobotNavigationService : IRobotNavigationService {
  public RobotNavigationService(IRobotFactory robotFactory) {
    //...
  }
}
public class RobotFactory : IRobotFactory {
  public IRobot Create(string nameOfRobot) {
    if (name == "Maximilian") {
      return new KillerRobot(); 
    } else {
      return new StandardRobot();
    }
  }
}

我的问题是在这里进行控制反转的正确方法是什么?我不想将 KillerRobot 和 StandardRobot 混凝土添加到 Factory 类中,对吗?而且我不想通过 IoC.Get<> 将它们引入对吗?公元前那将是服务位置不是真正的 IoC 对吗?有没有更好的方法来解决在运行时切换混凝土的问题?

4

4 回答 4

36

对于您的示例,您拥有完美的工厂实现,我不会更改任何内容。

但是,我怀疑您的 KillerRobot 和 StandardRobot 类实际上有它们自己的依赖项。我同意您不想将 IoC 容器暴露给 RobotFactory。

一种选择是使用 ninject 工厂扩展:

https://github.com/ninject/ninject.extensions.factory/wiki

它为您提供了两种注入工厂的方法 - 通过接口和注入返回 IRobot(或其他)的 Func。

基于接口的工厂创建示例:https ://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

基于 func 的示例:https ://github.com/ninject/ninject.extensions.factory/wiki/Func

如果您愿意,您也可以通过在 IoC 初始化代码中绑定一个 func 来实现。就像是:

var factoryMethod = new Func<string, IRobot>(nameOfRobot =>
                        {
                            if (nameOfRobot == "Maximilian")
                            {
                                return _ninjectKernel.Get<KillerRobot>();
                            }
                            else
                            {
                                return _ninjectKernel.Get<StandardRobot>();
                            }

                        });
_ninjectKernel.Bind<Func<string, IRobot>>().ToConstant(factoryMethod);

您的导航服务可能如下所示:

    public class RobotNavigationService
    {
        public RobotNavigationService(Func<string, IRobot> robotFactory)
        {
            var killer = robotFactory("Maximilian");
            var standard = robotFactory("");
        }
    }

当然,这种方法的问题在于您正在 IoC 初始化中编写工厂方法 - 也许不是最好的权衡......

工厂扩展试图通过为您提供几种基于约定的方法来解决这个问题 - 从而允许您通过添加上下文相关的依赖项来保留正常的 DI 链接。

于 2012-04-23T18:32:25.177 回答
4

你应该这样做的方式:

kernel.Bind<IRobot>().To<KillingRobot>("maximilliam");
kernel.Bind<IRobot>().To<StandardRobot>("standard");
kernel.Bind<IRobotFactory>().ToFactory();

public interface IRobotFactory
{
    IRobot Create(string name);
}

但是这种方式我认为你丢失了空名称,所以在调用时IRobotFactory.Create你必须确保通过参数发送正确的名称。

在接口绑定中使用ToFactory()时,它所做的只是使用 Castle(或动态代理)创建一个代理,该代理接收 IResolutionRoot 并调用 Get()。

于 2016-11-25T19:25:39.267 回答
3

我不想将 KillerRobot 和 StandardRobot 混凝土添加到 Factory 类中,对吗?

我建议你可能会这样做。如果不实例化具体对象,工厂的目的是什么?我想我可以看到你来自哪里 - 如果IRobot描述了一份合同,注射容器不应该负责创建它吗?这不就是容器的用途吗?

也许。然而,返回负责newing 对象的具体工厂似乎是 IoC 世界中非常标准的模式。我不认为有一个混凝土工厂做一些实际的工作是违反原则的。

于 2012-04-23T18:33:24.490 回答
0

我正在寻找一种方法来清理返回 C# 类以执行某些工作的大量 switch 语句(此处的代码异味)。

我不想将每个接口显式映射到 ninject 模块中的具体实现(本质上是冗长的 switch case 的模仿,但在 diff 文件中),所以我设置模块以自动绑定所有接口:

public class FactoryModule: NinjectModule
{
    public override void Load()
    {
        Kernel.Bind(x => x.FromThisAssembly()
                            .IncludingNonPublicTypes()
                            .SelectAllClasses()
                            .InNamespaceOf<FactoryModule>()
                            .BindAllInterfaces()
                            .Configure(b => b.InSingletonScope()));
    }
}

然后创建工厂类,实现 StandardKernal,它将通过使用 IKernal 的单例实例获取指定的接口及其实现:

    public class CarFactoryKernel : StandardKernel, ICarFactoryKernel{
    public static readonly ICarFactoryKernel _instance = new CarFactoryKernel();

    public static ICarFactoryKernel Instance { get => _instance; }

    private CarFactoryKernel()
    {
        var carFactoryModeule = new List<INinjectModule> { new FactoryModule() };

        Load(carFactoryModeule);
    }

    public ICar GetCarFromFactory(string name)
    {
        var cars = this.GetAll<ICar>();
        foreach (var car in cars)
        {
            if (car.CarModel == name)
            {
                return car;
            }
        }

        return null;
    }
}

public interface ICarFactoryKernel : IKernel
{
    ICar GetCarFromFactory(string name);
}

然后,您的 StandardKernel 实现可以通过您在装饰类的接口上选择的标识符访问任何接口。

例如:

    public interface ICar
{
    string CarModel { get; }
    string Drive { get; }
    string Reverse { get; }
}

public class Lamborghini : ICar
{
    private string _carmodel;
    public string CarModel { get => _carmodel; }
    public string Drive => "Drive the Lamborghini forward!";
    public string Reverse => "Drive the Lamborghini backward!";

    public Lamborghini()
    {
        _carmodel = "Lamborghini";
    }
}

用法:

        [Test]
    public void TestDependencyInjection()
    {
        var ferrari = CarFactoryKernel.Instance.GetCarFromFactory("Ferrari");
        Assert.That(ferrari, Is.Not.Null);
        Assert.That(ferrari, Is.Not.Null.And.InstanceOf(typeof(Ferrari)));

        Assert.AreEqual("Drive the Ferrari forward!", ferrari.Drive);
        Assert.AreEqual("Drive the Ferrari backward!", ferrari.Reverse);

        var lambo = CarFactoryKernel.Instance.GetCarFromFactory("Lamborghini");
        Assert.That(lambo, Is.Not.Null);
        Assert.That(lambo, Is.Not.Null.And.InstanceOf(typeof(Lamborghini)));

        Assert.AreEqual("Drive the Lamborghini forward!", lambo.Drive);
        Assert.AreEqual("Drive the Lamborghini backward!", lambo.Reverse);
    }
于 2017-11-16T21:16:50.163 回答