【问题标题】:How I can to avoid IoC locator in next case?在下一种情况下如何避免 IoC 定位器?
【发布时间】:2017-05-07 09:56:41
【问题描述】:

简而言之:我想通过实体中的道具解析接口。 我有自托管的 wcf 并使用 ninject 进行 DI。 例如我的工作代码:

//program.cs
...
private static StandardKernel CreateKernel()
{
    var kernel = new StandardKernel();

    kernel.Bind<IDbConnectionFactory>().ToMethod(c =>
        new OrmLiteConnectionFactory(
            conString, 
            SqlServerDialect.Provider))
        .InSingletonScope();
        
    kernel.Bind<IControllerProccessor>().To<ControllerProccessor>()
        .WhenInjectedInto<HelloWorldService>().WithConstructorArgument(kernel);
        
    kernel.Bind<IControllerProccessor>().To<Vendor1Proccessor>()
        .Named("vendor1"); 
        
    kernel.Bind<IControllerProccessor>().To<Vendor2Proccessor>()
        .Named("vendor2"); 

    return kernel;
}
...

//IControllerProccessor.cs
public interface IControllerProccessor
{
    SimpleController Ctr { get; set; }
    bool sendMsg(string msg);
}

//Vendor1Proccessor.cs
public class Vendor1Proccessor : IControllerProccessor
{
    public SimpleController Ctr {get; set;}

    public bool sendMsg(string msg)
    {
        //specific to vendor code, for example calls to vendor1 SDK
        Console.WriteLine("Controller id: {0} vendor:{1} recivied msg: {2}",
            Ctr.Id,
            "Vendor1Class",
            msg);
        return true;
    }
}

//Vendor2Proccessor.cs
public class Vendor2Proccessor : IControllerProccessor
{
    public SimpleController Ctr { get; set; }

    public bool sendMsg(string msg)
    {
        //specific to vendor code, for example calls to vendor1 SDK
        Console.WriteLine("Controller id: {0} vendor:{1} recivied msg: {2}",
            Ctr.Id,
            "Vendor2Class",
            msg);
        return true;
    }
}

//ControllerProccessor.cs
public class ControllerProccessor : IControllerProccessor
{
    public SimpleController Ctr {get; set;}

    private readonly IKernel kernel;

    public ControllerProccessor(IKernel _kernel)
    {
        kernel = _kernel;
    }

    public bool sendMsg(string msg)
    {
        var param = new Ninject.Parameters.PropertyValue("Ctr", Ctr);
        return kernel.Get<IControllerProccessor>(Ctr.Vendor, param).sendMsg(msg);
    }
}

//HelloWorldService.cs
public class HelloWorldService : IHelloWorldService
{
    private readonly IDbConnectionFactory dbFactory;
    private readonly IControllerProccessor ctrProccessor;

    public HelloWorldService(IDbConnectionFactory _dbFactory, IControllerProccessor _ctrProccesor)
    {
        dbFactory = _dbFactory;
        ctrProccessor = _ctrProccesor;
    }

    public bool sendMsgToAllControllers(string msg)
    {
        var db = dbFactory.Open();
        var controllers = db.Select<SimpleController>();

        foreach(var ctr in controllers)
        {
            ctrProccessor.Ctr = ctr;
            ctrProccessor.sendMsg(msg);
        }
        db.Close();

        return true;
    }

}

//SimpleController.cs
[DataContract]
[Alias("SimpleController")]
public class SimpleController
{
    [AutoIncrement]
    [DataMember]
    public int? Id { get; set; }
    [DataMember]
    public string Vendor { get; set; }
}

当我调用 sendMsgToAllControllers("TEST_MESSAGE") 控制台输出时:

Controller id: 2 vendor:Vendor1Class recivied msg: TEST MESSAGE
Controller id: 3 vendor:Vendor2Class recivied msg: TEST MESSAGE
Controller id: 4 vendor:Vendor2Class recivied msg: TEST MESSAGE

我如何重构上述实现,使其采用 DI 风格,并且不使用 IoC 定位器反模式(或者在我的情况下这不是反模式)?

将来我会将实现(vendor1、vendor2 等)移动到单独的程序集中并进行运行时绑定。 (这里我要插件系统)

如果有任何改进我的代码的建议,我也将不胜感激。非常感谢。

修改实现

经过思考过程,我得出以下结论: 我删除了 ControllerProcessor 类,而是创建了 ControllerProcessorFactory:

public class ControllerProcessorFactory : IControllerProcessorFactory
{        
    private readonly IResolutionRoot resolutionRoot;

    public ControllerProcessorFactory(IResolutionRoot _resolutionRoot)
    {
        resolutionRoot = _resolutionRoot;
    }

    public IControllerProcessor Create(SimpleController ctr)
    {
        IControllerProcessor processor = resolutionRoot.Get<IControllerProcessor>(ctr.Vendor);
        processor.Ctr = ctr;
        return processor;
    }
}

在绑定中:

kernel.Bind<IControllerProcessorFactory>().To<ControllerProcessorFactory>();
kernel.Bind<IControllerProcessor>().To<Vendor1Processor>()
    .Named("vendor1");
kernel.Bind<IControllerProcessor>().To<Vendor2Processor>()
    .Named("vendor2"); 

用法(wcf类):

public class HelloWorldService : IHelloWorldService
{
    private readonly IDbConnectionFactory dbFactory;
    private readonly IControllerProcessorFactory ctrProcessorFactory;

    public HelloWorldService(IDbConnectionFactory _dbFactory, IControllerProcessorFactory _ctrProcFactory)
    {
        dbFactory = _dbFactory;
        ctrProcessorFactory = _ctrProcFactory;
    }

    public bool sendMsgToAllControllers(string msg)
    {
        var db = dbFactory.Open();
        var controllers = db.Select<SimpleController>();

        foreach(var ctr in controllers)
        {
            var ctrProcessor = ctrProcessorFactory.Create(ctr);
            ctrProcessor.sendMsg(msg);
        }
        db.Close();

        return true;
    }
}

【问题讨论】:

  • 对容器的依赖是否是[an anti-pattern](blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern) 取决于role it plays in your application。简而言之:如果依赖容器的代码是Composition Root的一部分,就可以了;否则就是个问题。
  • @Steven Thx,我阅读了这些文章,所以我在这里。如何重构我现有的代码?
  • 您的ControllerProccessor 似乎没有任何业务逻辑,因此您可以轻松地将ControllerProccessor 移动到您的Composition Root 中;这将解决问题。
  • @Steven 感谢您的帮助。 program.cs 不是组合根吗?我在绑定中定义了 ControllerProccesor,而且我只在构造函数上使用它。而我在 ControllerProccessor return kernel.Get&lt;IControllerProccessor&gt;(Ctr.Vendor, param).sendMsg(msg); 中使用的事实不被视为服务定位器?

标签: c# wcf dependency-injection ninject service-locator


【解决方案1】:

在他们的文档中查看动态模块加载。 即

    kernel.Load("*.dll");

但请确保您在启动时执行此操作,以免在运行时压倒您的系统。 至于你的模式,我建议在内核上使用 GetAll() 方法,因为这会给你更多的灵活性和控制

    IEnumerable<IControllerProccessor> processors = kernel.GetAll<IControllerProccessor>();
    foreach(var processor in processors)
        processor.sendMsg(....);

【讨论】:

  • 但是 kernel.GetAll() 如何将 SimpleController 实体放入 Ctr 属性?在处理器中,我需要使用来自 DB 的 SimpleController 数据。
【解决方案2】:

kernel.Get&lt;IControllerProccessor&gt;(Ctr.Vendor, param) 是服务定位器, 但话又说回来,这并不总是意味着“它”是一个问题。如果它很容易互换,那么这没什么大不了的(至少这是某些人的看法)。容易互换?创建一个特定的工厂接口,其唯一职责是返回所有处理器。 然后实现将完全由return kernel.Get&lt;IControllerProccessor&gt;(Ctr.Vendor, param); 组成。只要实现是组合根的一部分,这种对 ninject 的特定依赖就可以了。

较短的替代方案

现在,老实说,您的设计在我看来过于复杂,但话又说回来,我不知道所有细节。所以现在我只是使用 name 参数,但你可以轻松地(几乎)添加参数,它仍然可以工作:

只需注入Func&lt;string, IControllerProcessor&gt; 而不是内核:

public ControllerProccessor(
     Func<string,IControllerProcessor>> controllerProcessorFactory)

然后您可以按如下方式指定绑定:

private static IControllerProcessor CreateSpecificControllerProcessor(
    IResolutionRoot resolutionRoot, string vendorName)
{
    return resolutionRoot.Get<IControllerProcessor>(vendorName);
}

Bind<Func<IControllerProcessor>()
    .ToConstant(vendorName => CreateSpecficiControllerProcessor(this.Kernel, vendorName));

除了为Func 指定绑定之外,可能可以使用Ninject.Extensions.Factory。但请注意,当您使用此扩展时,将无法再手动 Bind 任何 Func(绑定将被扩展生成机制覆盖)。

【讨论】:

  • 谢谢,这与我需要的非常相似。但是当我输入kernel.Bind&lt;Func&lt;IEnumerable&lt;IControllerProcessor&gt;&gt;&gt;().ToConstant(vendorName =&gt; CreateSpecificControllerProcessor(kernel, vendorName)); 时出现错误:The type arguments for method 'Ninject.Syntax.IBindingToSyntax&lt;System.Func&lt;System.Collections.Generic.IEnumerable&lt;wcfService.IControllerProcessor&gt;&gt;&gt;.ToConstant&lt;TImplementation&gt;(TImplementation)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
  • 也是经过深思熟虑,我在 IControllerProcessorFactory 上替换了 ControllerProcessor,见我的帖子更新。
  • @Vilix 是的,对不起,在那里犯了一个错误代码中不应该有IEnumerable(我很困惑,因为我在看 ewassefs 的答案)。您选择的解决方案正是我想要提出的。 Func 替代方案基本上是相同的。由于工厂界面更加明确,我会坚持使用您选择的工厂界面。提示:最好使用 ctor-injection 而不是 processor.Ctr = ctr
  • @Vilix 这可以通过在resolutionRoot.Get 调用中添加TypeMatchingConstructorArgument 来完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-14
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 1970-01-01
  • 2013-07-17
相关资源
最近更新 更多