具有值类型和对象类型依赖性的 IoC
我正在寻找有关为 IoC 设计对象的最佳方法的建议
假设我有一个对象(服务),它依赖于在 Ioc 中注册的 DataContext。
但它也需要一个 name 属性,我可以这样设计对象:
class Service
{
public Service(IDataContext dataContext,
string name)
{
this._dataContext = dataContext;
this._name = name
}
public string Name
{
get
{
return _name;
}
}
}
问题是与 Ioc 容器一起使用变得非常复杂,因为像 name 这样的字符串对象不容易注册,并且与 Ioc 容器一起使用变得复杂: 因此,解析变得令人困惑:
var service = Ioc.Resolve<Service>( ?? )
另一种方法是按如下方式设计:
class Service
{
public Service(IDataContext dataContext)
{
this._dataContext = dataContext;
}
public string Name { get; set; }
}
解析现在更容易:
var service = Ioc.Resolve<Service>();
service.Name = "Some name";
唯一的缺点是不再需要指定名称。 我想听听 DI 或 IoC 专家的意见,他们将如何设计这个,并且仍然对具体的 Ioc 容器技术保持相当不可知的态度。
我知道这很大程度上取决于你想如何使用它,如果名称确实是可选的,选项 2 将是完美的。但在需要名称的情况下,您可以在代码中的另一点添加验证步骤,而是进行设计以使 Ioc 更简单。
想法?
I am looking for suggestions as to the best way to design objects for IoC
Suppose I have an object (Service) that has a dependency to a DataContext which is registered with Ioc.
But it also requires a name property, i could design the object like this:
class Service
{
public Service(IDataContext dataContext,
string name)
{
this._dataContext = dataContext;
this._name = name
}
public string Name
{
get
{
return _name;
}
}
}
The problem is it becomes very complicated to use with Ioc containers as a string object such as name is not easy to register and the usage becomes complicated with the Ioc container:
So resolution becomes confusing:
var service = Ioc.Resolve<Service>( ?? )
Another approach is to design it as follows:
class Service
{
public Service(IDataContext dataContext)
{
this._dataContext = dataContext;
}
public string Name { get; set; }
}
The resolution is now easier:
var service = Ioc.Resolve<Service>();
service.Name = "Some name";
The only downsite is specifying the name is no longer required.
I would like to hear from DI or IoC experts how they would go about designing this and still stay fairly agnostic to the concrete Ioc container technology.
I know that a lot depends on how you want to use this, option 2 would be perfect if name really was optional. But in the case where name is required you could add the validation step at another point in code, but rather go for the design to make Ioc simpler.
Thoughts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您对 DI 容器的选择不应决定 API 的设计。如果
name
不是可选的,它应该是构造函数签名的一部分(从而使其成为必需的)。下一个问题是如何在不产生大量开销的情况下配置容器。如何做到这一点取决于容器。以下是如何在 Castle Windsor 中围绕字符串参数实现约定:
然后向容器注册
NameConvention
,如下所示:如果您的容器没有适当的扩展点,请选择一个具有适当扩展点的容器。
Your choice of DI Container shouldn't dictate the design of your API. If
name
isn't optional, it should be part of the constructor's signature (thus making it mandatory).The next question then becomes how to configure the container without incurring tons of overhead. How to do that depends on the container. Here's how to implement a convention around a string argument in Castle Windsor:
Then register the
NameConvention
with the container like this:If your container doesn't have the appropriate extensibility points, pick a container that does.
大多数好的 IoC 容器都会在您进行配置时提供简单的方法来提供构造函数参数。
您的第一个示例 - 构造函数注入 - 通常被认为是首选方式。将构造函数视为需要遵循的契约,一旦满足,就会呈现一个有效的对象。
您的第二个代码示例(属性注入)通常被认为不如构造函数注入更可取。不管怎样,IoC 容器通常都会让您能够在配置时为构造函数参数或属性提供值,每次您要求 IoC 创建该对象时都会提供这些值。
我不确定您要使用哪个 IoC 容器,但这里有一个用于配置 StructureMap 并为各种服务提供字符串值的代码示例。除非我误读了你的问题,否则这似乎就是你想要做的。
编辑
回答评论,如果您想实际手动提供构造函数参数,它看起来像这样:
显然这会很快变得丑陋,所以您可能需要启动一些工厂/帮助器方法,如果您发现自己经常这样做。
Most good IoC containers will provide easy ways to supply constructor arguments when you do your configuration.
Your first example—constructor injection—is usually considered the preferred way. Think of your constructor as a contract to be followed, which, once satisfied, renders a valid object.
Your second code sample—property injection—is usually considered less preferable to constructor injection. Either way though, IoC containers will usually give you the ability to provide values for constructor parameters or properties at configuration, which will be supplied each time you ask your IoC to create you that object.
I'm not sure which IoC container you're looking to use, but here's a sample of code used to configure StructureMap, and provide string values for various services. Unless I'm misreading your question, this seems to be what you're looking to do.
EDIT
Answering the comment, if you wanted to actually supply a constructor argument manually, it would look like this:
Obviously this can get ugly fast, so you may want to whip up some factory/helper methods if you find yourself doing this often.
在这种情况下,我通常采用的方法是注入设置对象而不是字符串,然后在构造函数中询问表示该字符串的属性。或者在某些情况下甚至更好,每当我需要该字符串属性时,我都会将其从该设置中取出,以便可以更改它(如果它确实是程序设置,则很有用)。
另一种选择是使用绑定注释之类的东西。我不知道您正在使用哪个依赖项注入框架,但这是如何在 guice (java) 框架,我目前正在使用它。
The approach I usually do in such situation is injecting settings object instead of string and then ask in constructor for property representing that string. Or in some cases even better whenever I need that string property I took it out of that settings, so that it can be changed (useful if it's really a program setting).
The other option is to use something like binding annotation. I don't know which dependency injection framework you are using, but here is how it can be done in guice (java) framework, which I am currently working with.
如果您使用 Castle Windsor,则可以使用类型化工厂,您可以阅读有关 此处。本质上,类型化工厂允许您创建如下所示的界面。
注入它并使用您选择的名称调用
Create()
Windsor 将返回构造的IService
实现。If you are using Castle Windsor you can use typed factories, which you can read about here. Essentially, typed factories allow you to create an interface that looks like this.
Injecting this and calling
Create()
with the name of your choice Windsor will return a constructedIService
implementation.像往常一样进行设计,牢记良好的工程实践(SOLID 等)。然后,如果您选择的容器限制了您,那么您要么没有正确使用它,要么使用了错误的容器。
在 Windsor 的情况下,您可以在注册时轻松地向组件提供内联、硬编码的依赖项:
如果您需要在编译后更改它们,您还可以提供更多动态依赖项或依赖于 XML 配置中的值。
如果该值是可选的,则通过使用为其指定默认值的构造函数、使用构造函数重载或作为属性,将其设为可选。同样,如果您当前使用的容器可能没有切换到更好的容器,那么好的容器将处理所有这些情况。
Design it like you always would, keeping good engineering practices in mind (SOLID etc). Then if your container of choice constraints you, you're either not using it properly, or you're using wrong container.
In Windsor's case you can easily provide inline, hardcoded dependencies to components at registration time:
You can also provide more dynamic dependencies or depend on values from your XML config if you need to change them post-compilation.
If the value is optional then make it optional, either by using constructor that specifies a default value for it, by using constructor overloads or as a property. Again, good container will handle all of those cases, if the one you're currently using doesn't perhaps switch to a better one.