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

问题描述 投票:10回答:6

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

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

将字符串注入构造函数可以通过

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

此代码闻到了吗?

关于我在做什么的任何想法或改进?

c# dependency-injection ninject
6个回答
7
投票

我希望在这里使用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>()));

2
投票

此代码闻到了吗?

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

我也对此代码有问题:

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

一个IoC容器通常不用于创建域实体,而是用于创建服务/存储库/控制器等。即,用于创建控制应用程序流程的对象。


0
投票

我竭尽全力避免注入原始类型。

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


0
投票

似乎这里有两个问题:

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

这些是完全独立的问题。首先,依赖是依赖。它可以是复杂的,也可以是原始的,但是对依赖性的管理应保持一致。If you are using an IoC Container, it is absolutely not a code-smell to inject primitives.IoC容器和组合根是代码的特权部分,它们了解所有服务的需求以及如何满足它们。从概念上讲,这适用于复杂和原始类型。但是,实际上,存在用于注册复杂依赖关系与原始依赖关系的不同机制。您列出的参数名称方法完全有效。其他方法取决于参数索引。两者之间的选择是一头雾水,但取决于您是否希望自由地重命名构造函数自变量或重新排列构造函数自变量而不更改连线代码。

替代方法是具有具体的依赖性(或更糟糕的是,Service Locator模式),这是一个难以辨认的泥球的滑坡。

其次,是否应该将两个字符串组成一个类型取决于这两个值一起使用的频率以及您想要的DRY程度。在某些时候,追求干性的收益递减。另外,如果值总是注入在一起,则可以考虑简单地重构Ninject配置调用。无论您选择什么,都要确保基本原理在整个代码库中都是一致的。

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


0
投票

这是一个更通用的答案,但是与其直接注入一个字符串,不如创建一个接口,一个提供程序并注入提供程序本身,通常是一个更好的主意。因此,将来,如果您需要更改读取和创建字符串的方式,将会更加容易。 (考虑您今天正在从app.config中读取它,但是随后决定将其存储在DB中,或者从注册表中读取它,或者从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()

以这种方式,如果您更改获取字符串的方式,则仅更改方法本身或创建另一个要注入的方法。


-1
投票

考虑一个专用于保持所有接口用于注射的组件。在我当前的工作解决方案(使用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中的拼写错误。我搞砸了没关系,因为没有开发人员应该再次键入它。我仅注意到此错误,因为我在粘贴字符串时扫描了字符串。

© www.soinside.com 2019 - 2024. All rights reserved.