我有一些使用ninject注入依赖项的代码,这些依赖项是实际的字符串。这是一种注入字符串而不是创建新对象的反模式。
即。我想注入用户名和密码,创建一个名为凭证的小类实际上会更好用2个本地的Usernamd和密码并注入它吗?
可以通过
将字符串注入构造函数kernel.Bind<IUser>().To<User>()
.WithConstructorArgument(@"username", configuration.Username)
.WithConstructorArgument(@"password", configuration.Password);
这个代码有异味吗?
我正在做什么的任何想法或改进?
答案 0 :(得分:5)
我更愿意在这里使用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>()));
答案 1 :(得分:2)
这个代码有异味吗?
是。创建ConfigurationRepository
或创建工厂/构建器(两种不同的设计模式),创建不同的服务,然后在容器中注册该工厂/构建器。
我也遇到了这个代码的问题:
kernel.Bind<IUser>().To<User>()
.WithConstructorArgument(@"username", configuration.Username)
.WithConstructorArgument(@"password", configuration.Password);
IoC容器主要不用于创建域实体,而是用于创建服务/存储库/控制器等,即创建控制应用程序中流的对象。
答案 2 :(得分:0)
我竭尽全力避免注入原始类型。
查看您发布的代码,我的第一直觉是创建“IConfigurationService”并根据需要注入。该服务将包含用户名和密码的属性。
答案 3 :(得分:0)
这里似乎有两个问题:
这些是完全不同的问题。首先,依赖是一种依赖。它可以是复杂的或原始的,但依赖关系的管理应该是一致的。 If you are using an IoC Container, it is absolutely not a code-smell to inject primitives. IoC容器和组合根是代码的特权部分,它们了解所有服务的需求以及如何满足它们。从概念上讲,这适用于复杂和原始类型。然而,实际上,存在用于注册复杂依赖性与原始依赖性的不同机制。您列出的参数名称方法完全有效。其他方法取决于参数索引。在两者之间进行选择是分裂头发,但归结为是否要自由重命名构造函数参数或重新排序构造函数参数而不改变连接代码。
替代方案是具有具体的依赖性(或更糟糕的是,服务定位器模式),这是一个难以辨认的泥球的滑坡。
其次,两个字符串是否应该组成一个类型取决于这些值一起使用的频率以及你想要的DRY。在某些时候,追求干旱的收益递减。此外,如果值始终一起注入,您可以考虑简单地重构Ninject配置调用。无论您选择什么,请确保整个代码库的基本原理是一致的。
最后一点,使用IoC Containers管理实体被视为代码气味。实体通常负责在执行域操作时维护域不变量。 (不确定示例代码段的上下文/意图是什么,因此无法提供替代方案。)
答案 4 :(得分: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()
这样,如果您更改获取字符串的方式,则只需更改方法本身或创建另一个要注入的方法。
答案 5 :(得分:-1)
考虑一个专门用于保存注入所有接口的程序集。在我当前的工作解决方案(prism,使用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
中的拼写错误。我搞砸了并不重要,因为没有开发人员需要再次输入它。我只注意到这个错误,因为我在这里粘贴字符串时扫描了字符串。