【问题标题】:How can I get Ninject to inject a specific SerialPort instance into a specific instance of another class?如何让 Ninject 将特定的 SerialPort 实例注入到另一个类的特定实例中?
【发布时间】:2016-03-04 22:03:56
【问题描述】:

[适用于 Raspberry Pi 2 上的 Windows 10 IoT UWP 应用]:

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

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

我的 DeviceDriver 基本上是这样的:

public class DeviceDriver :IDeviceDriver
{
    ISerialPort m_localPort;

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

    // Other stuff
    // ...
}

任何人有任何想法我该怎么做?以下链接是我发现的唯一内容,但他们正在谈论 Unity 和 XML 配置文件,对于我想要做的事情来说似乎过于复杂。

Initialising configurable objects with dependency injection container

谢谢!

【问题讨论】:

  • this 最近的问题讨论了非常相似的事情。
  • 您希望如何使用这些端口?您是否只需要在某个时候收集两个不同的DeviceDrivers 及其各自的端口?或者你需要知道哪个是“COM1”,哪个是“COM2”?相同的ISerialPort "COM1" 是注入多个类还是只注入一次DeviceDriver 的"COM1"?
  • 我需要知道哪个DeviceDriver是COM1,哪个是COM2。更具体地说,DeviceDriver 用于控制带有 RS-232 端口的伺服电机。所以我有一个电机连接到 COM1,另一个相同的电机连接到 COM2。 COM1 专用于该电机,永远不会传递给另一个类或实例。目标是创建一个 DeviceDriver 实例,说明“这是电机 #1,它连接到 COM1”。然后我想创建一个 DeviceDriver 实例,它将是 Motor #2 连接到 COM2。
  • 好的,在这种情况下,您可以使用我的答案中显示的“命名绑定”或“工厂”方法。

标签: dependency-injection serial-port driver ninject windows-10-universal


【解决方案1】:

假设我们有以下实现:

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 可能无法在树莓派上运行。 如果是这种情况,您可能需要自己实现工厂:

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

【讨论】:

  • 谢谢!当我回到我的开发计算机时会试试这个!
  • 命名绑定为我解决了这个问题,非常感谢!我从您的示例中发现,我所知道的另一个缺失部分是使用 shouldInherit=true 传递构造函数参数。 EG - DeviceDriver 可以绑定 .WithParameter(new ConstructorArgument("baudRate", baudratevalue, true)),并且该值被传递给它使用的 SerialPort。再次感谢!
【解决方案2】:

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

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

编辑: 如果您打算使用 Ninject,那么我将通过将 ISerialPortFactory 注入 DeviceDriver 的构造函数并使用它来创建您的 ISerialPort 对象来解决这个问题。由于您的 DeviceDriver 类不关心它使用的是哪个 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 类如下所示:

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

抽象工厂方法是一种获取您想要的东西的笨拙方式,但由于您可以完全控制它,因此它是获得您需要的东西的可靠方式。它的主要用例是解决依赖关系,在这种依赖关系中,您在运行时之前不一定知道您想要的确切实现。

【讨论】:

  • 该功能在ninject中不可用。
  • 谢谢,当我回到我的开发计算机时会更深入地检查这个!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-23
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 2011-11-20
相关资源
最近更新 更多