依赖注入(ninject)使用字符串,反模式

本文关键字:字符串 模式 注入 ninject 依赖 | 更新日期: 2023-09-27 18:30:52

我有一些代码使用 ninject 来注入依赖项,这些依赖项是实际的字符串。例如,这是注入字符串而不是创建新对象的反模式吗?

即我想注入用户名和密码,实际上最好创建一个名为凭据的小类,其中包含 Usernamd 和密码的 2 个属性并注入它?

字符串注入构造函数可以通过以下方式完成

kernel.Bind<IUser>().To<User>()
      .WithConstructorArgument(@"username", configuration.Username)
      .WithConstructorArgument(@"password", configuration.Password);

这是代码气味吗?

对我正在做的事情有任何想法或改进吗?

依赖注入(ninject)使用字符串,反模式

我更愿意在这里使用ToMethod()

kernel.Bind<IUser>()
      .ToMethod(ctx => new User(configuration.Username, configuration.Password));

如果User构造函数有其他依赖项,那么我会听从@jgauffin的回答。

您仍然可以将ToMethod()Kernel一起使用:

kernel.Bind<IUser>()
      .ToMethod(ctx => new User(configuration.Username,
                                configuration.Password,
                                ctx.Kernel.Get<Foo>()));

这是代码气味吗?

是的。创建一个ConfigurationRepository或创建一个工厂/生成器(两种不同的设计模式),以创建不同的服务,然后在容器中注册该工厂/生成器。

我也遇到了这段代码的问题:

kernel.Bind<IUser>().To<User>()
      .WithConstructorArgument(@"username", configuration.Username)
      .WithConstructorArgument(@"password", configuration.Password);

IoC 容器主要用于创建域实体,而是用于创建服务/存储库/控制器等,即创建控制应用程序中流的对象。

我竭尽全力

避免注入基元类型。

看看你发布的代码,我的第一直觉是创建一个"IConfigurationService"并根据需要注入它。该服务将包含用户名和密码的属性。

这里似乎有两个问题:

  1. 注入原语是代码气味/反模式吗?
  2. 是否应该创建某种类型来组成用户名和密码字符串?

这些都是完全独立的问题。首先,依赖关系就是依赖关系。它可以是复杂的或原始的,但依赖项的管理应该是一致的。如果您使用的是 IoC 容器,则注入基元绝对不是代码异味。IoC 容器和组合根是代码的特权部分,它们了解所有服务的需求以及如何满足它们。从概念上讲,这适用于复杂类型和基元类型。但是,实际上,注册复杂依赖项与原始依赖项有不同的机制。您列出的参数名称方法是完全有效的。其他方法取决于参数索引。在两者之间进行选择是分裂头发,但归结为您是否希望自由重命名构造函数参数或重新排序构造函数参数而不更改连接代码。

另一种方法是具有具体的依赖关系(或者更糟的是,服务定位器模式),这是一个滑坡到一个难以辨认的泥球。

其次,是否应将这两个字符串组合成一个类型取决于这些值一起使用的频率以及您希望的 DRY 程度。在某些时候,追求枯燥的回报会递减。此外,如果这些值总是一起注入,您可以考虑简单地重构 Ninject 配置调用。无论您选择什么,请确保基本原理在整个代码库中保持一致。

最后一点,使用 IoC 容器管理实体被视为代码异味。实体通常负责在执行域操作时维护域不变量。(不确定示例代码片段的上下文/意图是什么,因此无法提供替代方案。

这是一个更通用的答案,但与其直接注入字符串,不如创建一个接口、一个提供程序并注入提供程序本身。因此,将来,如果您需要更改读取和创建字符串的方式,那会更容易。(假设您今天正在从 app.config 读取它,但随后您决定将其存储在数据库中,或者从注册表读取它或从 Web 请求传递它。示例应用程序:

 public interface IMachineIdProvider
    {
//Assume we are returning a machine Id.
        String ResolveMachineId();
    }

和实施。假设我们正在从 webconfig 读取它

 public class MachineIdProvider : IMachineIdProvider
    {
        private string _machineId;
        public string ResolveMachineId()
        {
            if (String.IsNullOrEmpty(_machineId))
            {
                this._machineId=  ConfigurationManager.AppSettings["MachineId"];
            }
            return this._machineId;
        }
    }

并以这种方式注入它:

kernel.Bind<IMachineIdProvider>().To<MachineIdProvider>().InSingletonScope();

使用接口声明类

public class MyWonderfulController  : BaseController
{
    private IMachineIdProvider _machineIdProvider;
    public MyWonderfulController(IMachineIdProvider machineIdProvider)
    {
       this._machineIdProvider = machineIdProvider;
    }
}

因此,无论您在哪里需要使用它,都可以调用

var id = _machineIdProvider.ResolveMachineId()

这样,如果更改获取字符串的方式,则只需更改方法本身或创建另一个方法进行注入。

考虑一个专门用于保存所有注入接口的程序集。在我当前的工作解决方案(棱镜,使用 MEF)中,我有一个类,它为您指定的用途声明常量字符串。

为什么要在文字上使用常量?因为它是一个符号;它是可重构的,可发现的,字符串的实际内容无关紧要。我总是使用 GUID 作为字符串的后缀,以便我可以"保证"唯一性。常量也可用于属性修饰。

/// <summary>
/// Exposes top level application region names for use when registering views.
/// </summary>
public static class Regions
{
    public const string WORKSPACE_REGION = "WorkspaceRegion {4CCDA460-D1A8-4BCE-863A-593021079F02}"; //names include a GUID to prevent clashes.
    public const string TOOL_DIAGRAM_REGION = "ToolDiagramRegigon {AD3CED71-C49D-4BD8-86FF-57E5F35116D3}";
    public const string STATUS_REGION = "StatusRegion {4CEF7A12-1E92-4EED-BD46-F70F07E27662}";
    public const string TOOLS_REGION = "ToolsRegion {3C6F99B2-6414-4E06-ACC5-7445944FFA29}";
    public const string HARDWARE_INTERFACE_REGION = "HardwareInterfaceRegion {4F16ECD1-D3F5-4BE2-BB00-DD148BAE8A83}";
}

请注意 TOOL_DIAGRAM_REGION 中的拼写错误。我搞砸了并不重要,因为任何开发人员都不需要再次键入它。我只注意到这个错误,因为我在粘贴到这里时扫描了字符串。