【问题标题】:IoC (Ninject) and FactoriesIoC (Ninject) 和工厂
【发布时间】:2012-04-23 18:14:03
【问题描述】:

如果我有以下代码:

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 对吗?有没有更好的方法来解决在运行时切换混凝土的问题?

【问题讨论】:

  • 可能要检查您的代码 - 第一行是不合法的 C#。
  • 对不起。感谢您的提醒。以为我在发布之前解决了这个问题。现在更正。

标签: c# .net inversion-of-control ninject ioc-container


【解决方案1】:

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

但是,我怀疑您的 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

基于函数的示例: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 链接。

【讨论】:

  • 要去试用扩展 - 你卖给我了! +1 和回答
  • @BuddyJoe 曾经使用上面的工厂方法运行过代码吗?
  • 我做到了。如果我记得最后的代码非常接近这个。
【解决方案2】:

你应该做的方式:

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()。

【讨论】:

  • 是的,我喜欢这样做 +1
【解决方案3】:

我不想将 KillerRobot 和 StandardRobot 具体添加到 Factory 类中吗?

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

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

【讨论】:

  • 虽然他的示例代码没有说明,但我认为他的问题是KillerRobot和StandardRobot实际上还有进一步的依赖关系,需要ninject解决。
  • 如果 KillerRobot 和 Standard Robot 有依赖关系,你会如何处理?
  • 如果您在工厂扩展中使用基于接口或基于函数的方法,将发生递归依赖解析。
  • 问题:kernel.Bind().ToFactory(); - 这是否意味着它总是会从接口名称的前面剪掉 I,然后在同一个组件中为混凝土运气好?还是有更多的选择?
  • 它更复杂。 ToFactory() 实际上会通过反射查看您的界面并生成一个动态的“代理”工厂。例如,如果您有: public interface IBarFactory { Bar CreateBar(int x, int y);您的 bar 构造函数实际上可能如下所示: Bar(object Dependency1, object Dependency2, int x, int y)。
【解决方案4】:

我正在寻找一种方法来清理大量 switch 语句,该语句返回一个 C# 类来做一些工作(这里有代码味道)。

我不想将每个接口显式映射到 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);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 1970-01-01
    • 2016-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多