如何让 Ninject 将特定的 SerialPort 实例注入另一个 class 的特定实例?

How can I get Ninject to inject a specific SerialPort instance into a specific instance of another class?

[这是针对 Raspberry Pi 2 上的 Windows 10 IoT UWP 应用]:

具体来说,我正在尝试创建两个串行端口,并且 link 每个串行端口都连接到具有串行连接的设备的设备驱动程序(我有两个相同的设备和一个串行端口来与每个设备通信) .我有一个 class(称之为 DeviceDriver),它为这种类型的设备实现了一个 IDeviceDriver 接口。我的硬件配置还包括一个具有多个串行端口的外部芯片。我有一个 class SerialPort,它们实现了一个 ISerialPort 接口。

我需要两个 DeviceDriver 实例,因为我有两个设备,还有两个 SerialPort 实例 - 每个设备一个。我可以获得 Ninject 来创建一个串行端口并将 ISerialPort 对象传递给 DeviceDriver 构造函数。我卡住的地方是我想要两个 DeviceDriver 对象;一个 linked 到串行端口 (COM1),另一个 linked 到一个单独的串行端口 (COM2)。 Ninject 的示例显示您可以将多个不同的 classes 绑定到一个实例(手里剑和剑都可以绑定到 IWeapon),但我不知道如何绑定 COM1 串行端口和 COM2 serialport 到 ISerialPort - 它给了我一个编译错误。那么我如何创建同一个 SerialPort class 的两个实例(使用不同的构造函数参数说一个是 COM1,另一个是 COM2,我已经知道如何指定构造函数参数)然后告诉 Ninject 哪个SerialPort 传递给 DeviceDriver 的两个实例 class,其中一个需要 COM1,一个需要 COM2?

我的 DeviceDriver 基本上是这样的:

public class DeviceDriver :IDeviceDriver
{
    ISerialPort m_localPort;

    public DeviceDriver(ISerialPort port)
    {
        m_localPort = port;
    }

    // Other stuff
    // ...
}

有人知道我该怎么做吗?以下 link 是我发现的唯一内容,但他们正在谈论 Unity 和 XML 配置文件,而且对于我要尝试做的事情来说,它似乎过于复杂。

Initialising configurable objects with dependency injection container

谢谢!

我不熟悉 Ninject 或 Unity,但温莎城堡有一种叫做 Pooled 的生活方式,它将创建最多指定数量的实例,然后 return 这些实例在它们被释放后到实例池中。使用这种生活方式时,Windsor 将根据指定的限制创建尽可能多的对象,然后回收实例(如果您从 IRecyclable 派生并实现了 Recycle() 方法)或正常处理它。

您可以使用提供正确构造函数参数的简单工厂方法创建您的组件,然后当它们 return 进入池时,它们将被正确配置。

编辑: 如果您打算使用 Ninject,那么我会通过将一个 ISerialPortFactory 注入 DeviceDriver 的构造函数并使用它来创建您的 ISerialPort 对象来解决这个问题。由于您的 DeviceDriver class 不关心它使用的是哪个 ISerialPort,因此可以使用工厂来管理您需要的实例。

你的工厂看起来像这样:

public interface ISerialPortFactory
{
    ISerialPort CreateNext();
}

public class SerialPortFactory : ISerialPortFactory
{        

    public ISerialPort CreateNext()
    {
        var serialPortConfiguration = GetNextConfiguration();
        return new SerialPort(serialPortConfiguration);
    }

    private GetNextConfiguration()
    {
        // you could manage some kind of internal registry of COMx configurations here
    }

}

您的客户端 DeviceDriver class 将如下所示:

public class DeviceDriver : IDeviceDriver
{
    public DeviceDriver(ISerialPortFactory factory)
    {
        m_localPort = factory.CreateNext();
    }
}

抽象工厂方法是一种获取所需内容的笨拙方法,但它是一种准确获取所需内容的可靠方法,因为您可以完全控制它。它的主要用例是解决依赖关系,您在运行时之前不一定知道您想要的确切实现。

假设我们有以下实现:

public class SerialPortAddress
{
    public SerialPortAddress(string address)
    {
        this.Address = address;
    }

    public string Address { get; }
}

public interface ISerialPort
{
    SerialPortAddress Address { get; }
}

public class SerialPort : ISerialPort
{
    public SerialPort(SerialPortAddress address)
    {
        this.Address = address;
    }

    public SerialPortAddress Address { get; }
}

public interface IDeviceDriver
{
    ISerialPort SerialPort { get; }
}

public class DeviceDriver : IDeviceDriver
{
    public DeviceDriver(ISerialPort serialPort)
    {
        SerialPort = serialPort;
    }

    public ISerialPort SerialPort { get; }
}

多次注射

然后我们可以按如下方式创建绑定并检索 IDeviceDrivers 及其串行端口的列表,如下所示:

public class Test
{
    [Fact]
    public void Bla()
    {
        var com1 = new SerialPortAddress("COM1");
        var com2 = new SerialPortAddress("COM2");

        var kernel = new StandardKernel();

        kernel.Bind<ISerialPort>().To<SerialPort>();
        kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
            .WithParameter(new TypeMatchingConstructorArgument(
                typeof(SerialPortAddress),
                (ctx, target) => com1, true));
        kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
            .WithParameter(new TypeMatchingConstructorArgument(
                typeof(SerialPortAddress),
                (ctx, target) => com2, true));

        var deviceDrivers = kernel.Get<List<IDeviceDriver>>();

        deviceDrivers.Should().HaveCount(2)
            .And.Contain(x => x.SerialPort.Address == com1)
            .And.Contain(x => x.SerialPort.Address == com2);
    }
}

另见 Multi Injection

命名绑定

或者,如果您需要知道哪个 IDeviceDrivers 是哪个,您也可以使用命名绑定:

[Fact]
public void NamedBindings()
{
    const string DeviceDriver1 = "DeviceDriver1";
    const string DeviceDriver2 = "DeviceDriver2";

    var com1 = new SerialPortAddress("COM1");
    var com2 = new SerialPortAddress("COM2");

    var kernel = new StandardKernel();

    kernel.Bind<ISerialPort>().To<SerialPort>();
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
        .Named(DeviceDriver1)
        .WithParameter(new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress), 
            (ctx, target) => com1, true));
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
        .Named(DeviceDriver2)
        .WithParameter(new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress),
            (ctx, target) => com2, true));

    kernel.Get<IDeviceDriver>(DeviceDriver1).SerialPort.Address.Should().Be(com1);
    kernel.Get<IDeviceDriver>(DeviceDriver2).SerialPort.Address.Should().Be(com2);
}

工厂

最后,您还可以通过工厂创建组件,这需要以工厂接口开头:

public interface IDeviceDriverFactory
{
    IDeviceDriver Create(SerialPortAddress address);
}

使用 Ninject.Extensions.Factory 我们现在可以执行以下操作:

[Fact]
public void Factory()
{
    var com1 = new SerialPortAddress("COM1");
    var com2 = new SerialPortAddress("COM2");

    var kernel = new StandardKernel();

    kernel.Bind<ISerialPort>().To<SerialPort>();
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>();
    kernel.Bind<IDeviceDriverFactory>()
          .ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());

    var factory = kernel.Get<IDeviceDriverFactory>();

    factory.Create(com1).SerialPort.Address.Should().Be(com1);
    factory.Create(com2).SerialPort.Address.Should().Be(com2);
}

编辑:Ninject.Extension.Factory 可能不会 运行 在 raspberry pi 上。 如果是这种情况,您可能需要自己实施工厂:

public class DeviceDriverFactory : IDeviceDriverFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public DeviceDriverFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public IDeviceDriver Create(SerialPortAddress address)
    {
        var serialPortAddressParameter = new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress),
            (ctx, t) => address)
        this.resolutionRoot.Get<IDeviceDriver>(serialPortAddressParameter);
    }
}

Bind<IDeviceDriverFactory>().To<DeviceDriverFactory>();