我读过许多关于在开发中使用 IoC 容器的有趣文章。在许多情况下,作者建议我们编写自己的简单包装器,它隐藏了读取容器的接口,如 Ninject、Castle.Windsor、Unity 等。
包装器的实现如下所示(对于 Ninject):
/// <summary>
/// Contains links for service contract and realization.
/// </summary>
public static class IoC
{
static readonly IKernel kernel;
static IoC()
{
kernel = new StandardKernel();
}
public static void RegisterService<T>(Type service)
{
kernel.Bind<T>().To(service);
}
public static T Resolve<T>()
{
return kernel.Get<T>();
}
}
好的,但是我如何使用高级这种模式有什么特点?例如,我喜欢使用生命周期管理修饰符,例如 InRequestScope() 或 InSingletonScope(),并且许多容器都支持相同的修饰符。通过上面的模式,我可以扔掉 ninject 并使用 15 行实施。差别在哪里呢?
我还查看了 CommonServiceLocator 试图找到丰富的包装器,但它与我的 IoC 类没有太大区别。
现在我认为我不应该制造自己的自行车并直接使用功能齐全的容器。你呢?
I've read many interesting articles about using IoC containers in the development. In many cases authors advise us to write your own simple wrapper which hides an interface to the read container like Ninject, Castle.Windsor, Unity etc.
The implementation of wrapper looks like this (for Ninject):
/// <summary>
/// Contains links for service contract and realization.
/// </summary>
public static class IoC
{
static readonly IKernel kernel;
static IoC()
{
kernel = new StandardKernel();
}
public static void RegisterService<T>(Type service)
{
kernel.Bind<T>().To(service);
}
public static T Resolve<T>()
{
return kernel.Get<T>();
}
}
Okay, but how can I use advanced features with this pattern? For example I love to use life-time managment modifiers like InRequestScope() or InSingletonScope() and many containers support the same. With the pattern above I can throw away ninject and use 15-lines implementation. Where is the difference?
I also looked at CommonServiceLocator trying to find rich wrapper but it has no much difference with mine IoC class.
Now I think that I should not make my own bike and use full featured containers directly. What about you?
发布评论
评论(3)
您的应用程序代码应该完全忽略 DI 容器的存在,无论是哪一个。相反,需要依赖项的类应该通过构造函数注入来请求它们,如下所示:
注意 Guard Clause 和
readonly
关键字的组合如何保护类的不变量。从 Foo 类的任何方法中,this.Bar
都保证可用。所有 DI 容器都理解这种设计模式,因此您可以让您选择的容器从 组合根。从这里您可以使用容器的所有高级功能,而不必担心代码库的其余部分会与容器耦合。
Your application code should be completely oblivious to the existence of a DI Container, no matter which one. Instead, classes that need dependencies should request them through Constructor Injection, like this:
Notice how the combination of the Guard Clause and the
readonly
keyword protects the invariants of the class. From any method of the Foo class,this.Bar
is guaranteed to be available.All DI Containers understand this design pattern, so you can let your container of choice compose the entire application graph from the Composition Root. From here you can use all the advanced features of the container without worrying that the rest of the code base becomes coupled to the container.
做了一些死灵术,但我认为其他答案部分地忽略了你的观点。
您一直在谈论“高级功能”,例如范围。虽然实际的实现没有引用容器,但它仍然在某种程度上依赖于这些功能。所以我同意,即使容器仅在组合根中使用,您的实现也可能不会 100% 与容器无关。如果是的话,那就意味着您在某些方面重新发明了轮子(例如请求范围...)
现在,如果您需要的只是“标准”范围,例如
Singleton
和 < code>Request - 许多容器都支持 - 使用抽象(自定义抽象)可能是值得的,这样您就可以轻松地更换容器(例如,您可能想要对它们进行基准测试,...) 。当您开始接触更高级的功能(例如自定义作用域、Ninject
.InCallScope()
、.InNamedScope()
)时,交换容器变得越来越麻烦。工作。因此“抽象”几乎没有什么用处,因为“抽象”对于容器来说是非常特定的。现在您应该彻底调查“锁定”到特定 DI 容器是否值得享受高级功能的好处。
您使用的容器功能越专业,切换容器的工作量就越多,可能很快就会变得不经济。它限制了你未来的选择。如果没有更新..你就被困住了。如果对于不断增长的用户群来说速度太慢......你就会陷入困境。
您必须平衡独立性、可测试性、可维护性、简单性(……也许还有更多标准)。
我认为在不了解您的具体情况的情况下,有人无法给您有效的答案。此外,这方面的经验往往取决于个人的经验。当一个人一直在开发框架时,他可能会说“从来没有”。如果一直在开发中型最终用户应用程序,人们可能会说这是值得的。
就我个人而言,我一直在使用高级功能,例如命名范围、调用范围、自定义范围、ninject.extensions.Factory(甚至是更强大的自定义版本)、拦截、上下文保留、上下文操作、条件绑定……很多。在整整 4 年的开发过程中(8 位开发人员),没有人对此感到后悔。只能说一个人能乐此不疲。但我仍然建议大家谨慎对待这个决定。我当然不是提倡的。
最后一件事。我所知道的最可怕的项目是一个,它试图把一切都做到完美,多次推迟发布,最后却花了很多钱,没有一分钱收入。创建一个糟糕的软件,赚钱,然后必须修复大量技术债务,这是很糟糕的。但与一开始就赚不到一毛钱相比,这是一个奢侈的问题。
Doing some necromancy, but I think the other answers missed your point - partially.
You've been talking about "advanced features" like scoping. While the actual implementation does not reference the container, it is still somewhat dependent on these features. So i agree that even if the container is only used in the composition root, your implementation is probably not going to be a 100% container agnostic. If it would be, it would mean that you're re-inventing the wheel in some regards (like request scoping...)
Now, if all you need is "standard" scoping like
Singleton
andRequest
- which are supported by lots of containers - it may be worthwile to use an abstraction (custom one) so you can easily swap out containers (for example, you might want to benchmark them,...).When you start approaching even more advanced features, like custom scopes, Ninject
.InCallScope()
,.InNamedScope()
it's getting more and more likely that swapping containers will be too much work. So there's little use for an "abstraction", because the "abstraction" is very specific to the container.Now you should investigate thoroughly whether "locking" into a specific DI container is worth the benefits of the advanced features.
The more specialised container features you use, the more work it will be to switch containers, probably making it uneconomical very fast. It limits your future choices. If there's no updates .. you're stuck. If it's too slow for a growing user-base .. you're stuck.
You'll have to balance independence, testability, maintainability, simplicity (... and maybe more criterias).
I don't think that someone can give you a valid answer to that without knowing your specifics. Also, experience in that regards tends to depend on one's own experience. When one has been developing frameworks, one's probably going to say "never ever". If one's been developing mid-scaled End-User applications one might say, that it was worth it.
Personally i've been using advanced features like named scope, call scope, custom scoping, ninject.extensions.Factory (+ even a more powerful custom version of it), interception, context preservation, context manipulation, conditional bindings,... a lot. And during a full 4 years of development (8 developers) no-one has regretted this. Just to say that one can be happy with it. But I'd still recommend everyone to be really careful about this decision. I'm certainly not advocating it.
One last thing. The most horrendous project i know of was one, that tried to do everything perfect, postponed it's release multiple times, and ended with a lot of spent money and not a single dime income. Creating a shitty software, making money, and then having to fix a lot of technical debt is bad. But it is a luxury problem compared to not making a single dime in the first place.
我不建议抽象你的 IOC 框架。对于 95% 的应用程序,您没有充分的理由需要这样做。
只需像这样公开内核:
如果您迫切希望抽象出您的 IOC 框架,那么我仍然可以在您需要时通过某种方式直接访问真正的框架。例如,您可以将抽象内核转换为实际内核。当然,只有当您想要使用抽象未提供的特定功能时,您才会偶尔这样做。
关于您从 NInject 发布的示例,需要注意一件事 - 我想说该静态 IOC 类的目的是提供对内核单例实例的访问,而不是向应用程序隐藏 NInject。
I wouldn't recommend abstracting your IOC framework. For 95% of apps there is no good reason why you would need to.
Just expose the kernel like this:
If you're desperate to abstract away your IOC framework, then I would still have some way of accessing the real framework directly when you need to. For example, you could cast the abstracted kernel to the actual kernel. Of course you would only do this occasionally when you wanted to make use of specific features that the abstraction doesn't provide.
One thing to note about the example you posted from NInject - I would say the purpose of that static IOC class is to provide access to a singleton instance of the kernel, not to hide NInject from the application.