关于单身人士的使用,似乎有一种耻辱。我从来没有亲自接受它,但为了开放的思想,我试图尝试将IoC概念作为替代方案,因为我坦率地厌倦了我的日常工作,并希望尝试不同的东西。如果我对IoC概念的解释不正确或误导,请原谅我。
情况就是这样:我正在Windows服务中构建一个基于HttpListener
的简单Web服务器,该服务利用插件模型来确定如何根据请求的URL处理请求(就像其他人一样)询问HttpListener
)。我发现插件的方法是查询已配置的目录,查找用HttpModuleAssemblyAttribute
修饰的程序集。这些程序集可以包含0个或更多IHttpModule
个子项,这些子项还使用HttpModuleAttribute
进行修饰,用于指定模块的名称,版本,人类可读描述和各种其他信息。类似的东西:
[HttpModule(/*Some property values that matter */)]
public class SimpleHttpModule : IHttpModule
{
public void Execute(HttpListenerContext context)
{
/* Do Something Special */
}
}
当发现HttpModule
时,我通常会将其添加到Dictionary<string, Type>
对象,其唯一目的是跟踪我们了解的模块。这本字典通常会出现在我的各种单身人士身上,它具有ACE style Singleton的角色(我从C ++那里学习单身人士的遗产)。
现在我想要实现的是使用(我的理解)一般IoC概念的类似内容。基本上我所拥有的是AppService
集合,IAppService
定义为:
public interface IAppService : IDisposable
{
void Initialize();
}
我的插件AppService
看起来像是:
[AppService("Plugins")]
internal class PluginAppService : IAppService, IDictionary<string, Type>
{
/* Common IDictionary Implementation consisting of something like: */
internal Type Item(string modName)
{
Type modType;
if (!this.TryGetValue(modName, out modType)
return null;
return modType;
}
internal void Initialize()
{
// Find internal and external plug-ins and add them to myself
}
// IDisposable clean up method that attempts to dispose all known plug-ins
}
然后在服务OnStart
期间,我实例化一个AppServices
的实例,该实例在本地已知,但传递给所有实例化插件的构造函数:
public class AppServices : IDisposable, IDictionary<string, IAppService>
{
/* Simple implementation of IDictionary */
public void Initialization()
{
// Find internal IAppService implementations, instantiate them (passing this as a constructor parameter), initialize them and add them to this.
// Somewhere in there would be something like
Add(appSvcName, appSvc);
}
}
我们曾经的单一方法实现成为一个抽象实现+子上的构造函数:
[HttpModule(/*Some property values that matter */)]
public abstract class HttpModule : IHttpModule
{
protected AppServices appServices = null;
public HttpModule(AppServices services)
{
appServices = services;
}
public abstract void Execute(HttpListenerContext context);
}
[HttpModule(/*Some property values that matter */)]
public class SimpleHttpModule : HttpModule
{
public SimpleHttpModule(AppServices services) : base(services) { }
public override void Execute(HttpListenerContext context)
{
/* Do Something Special */
}
}
对常用应用程序服务的任何访问都变为:
var plugType = appServices["Plugins"][plugName];
而不是:
var plugType = PluginManager.Instance[plugName];
我是否遗漏了一些基本的IoC概念,可以简化这一切,或者是否真的有益于所有这些额外的代码?在我的世界中,单身人士是一种简单的生物,它允许整个程序中的代码访问所需的(相对静态的)信息(在这种情况下是类型)。
更明确地提出问题:
答案 0 :(得分:4)
IoC是一个通用术语。如今,依赖注入是更受欢迎的术语。
依赖注入在几种情况下确实很有用。首先,它定义了一个比具有硬编码依赖实例的解决方案更可测试的架构。单身人士难以进行单元测试,因为他们是静态的,静态数据不能“卸载”。
其次,依赖注入不仅会实例化您想要的类型,还会实例化所有依赖类型。因此,如果A类需要B类,而B类需要C类和D类,那么一个好的DI框架将自动创建所有依赖项,并控制它们的生命周期(例如,使它们在单个Web请求的生命周期内生效)。
DI容器可以作为通用工厂,可以实例化任何类型的对象(只要它已正确配置并满足DI框架的要求)。所以你不必编写自定义工厂。
与任何通用解决方案一样,它旨在为90%的用例提供所需的功能。当然,您可以在每次需要集合时创建手工制作的自定义链接列表数据结构,但是90%=通用工具的工作正常时间的百分比。 DI和Custom Factories也是如此。
答案 1 :(得分:3)
PluginManager
的类进行单元测试?
修改强>
仅仅因为你可以用单例实现相同的功能并不意味着它易于维护。通过使用IoC(至少这种带有构造函数的样式),您可以明确地声明对象具有的依赖关系。通过使用单例,信息隐藏在课堂中。它还使用替代实现替换这些依赖项变得更加困难。
因此,使用单例PluginManager
,很难使用模拟插件测试您的HTTP服务器,而不是从磁盘上的某个位置查找它们。使用IoC版本,您可以传递IAppService
的备用版本,它只是从预先填充的Dictionary
中查找插件。
答案 2 :(得分:0)
虽然在这种情况下我仍然不确定IoC / DI是更好,但我确实看到了项目范围的好处。对于诸如日志记录和可配置性之类的事情,它肯定是正确的方法。
我期待在未来的项目中更多地尝试它。