在下一种情况下如何避免 IoC 定位器?

How I can to avoid IoC locator in next case?

简而言之:我想通过实体中的道具解析接口。 我有自己托管的 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 等)并进行运行时绑定。 (这里我要插件系统)

我也非常感谢任何改进我的代码的建议。非常感谢。

实施的修改

经过思考,我得出以下结论: 我删除了 ControllerProccessor class 而不是我创建了 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 class):

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

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

    kernel.Load("*.dll");

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

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

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

更短的选择

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

只需注入一个 Func<string, IControllerProcessor> 而不是内核:

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(绑定将被扩展生成机制覆盖)。