IoC优于Factory Singleton的好处

时间:2012-01-31 03:57:35

标签: c# singleton inversion-of-control

关于单身人士的使用,似乎有一种耻辱。我从来没有亲自接受它,但为了开放的思想,我试图尝试将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概念,可以简化这一切,或者是否真的有益于所有这些额外的代码?在我的世界中,单身人士是一种简单的生物,它允许整个程序中的代码访问所需的(相对静态的)信息(在这种情况下是类型)。

更明确地提出问题:

  1. 这是转换为IoC / DI概念的Factory Singleton的有效实现吗?
  2. 如果是,那么所需额外代码的回报/利益在哪里,以及强加看似更笨重的API?

3 个答案:

答案 0 :(得分:4)

IoC是一个通用术语。如今,依赖注入是更受欢迎的术语。

依赖注入在几种情况下确实很有用。首先,它定义了一个比具有硬编码依赖实例的解决方案更可测试的架构。单身人士难以进行单元测试,因为他们是静态的,静态数据不能“卸载”。

其次,依赖注入不仅会实例化您想要的类型,还会实例化所有依赖类型。因此,如果A类需要B类,而B类需要C类和D类,那么一个好的DI框架将自动创建所有依赖项,并控制它们的生命周期(例如,使它们在单个Web请求的生命周期内生效)。

DI容器可以作为通用工厂,可以实例化任何类型的对象(只要它已正确配置并满足DI框架的要求)。所以你不必编写自定义工厂。

与任何通用解决方案一样,它旨在为90%的用例提供所需的功能。当然,您可以在每次需要集合时创建手工制作的自定义链接列表数据结构,但是90%=通用工具的工作正常时间的百分比。 DI和Custom Factories也是如此。

答案 1 :(得分:3)

当你开始编写单元测试时,IoC会变得更有趣。很抱歉回答更多问题的问题,但是......对于这两种实现,单元测试会是什么样的?您是否能够在没有从磁盘中查找程序集的情况下对使用PluginManager的类进行单元测试?

修改

仅仅因为你可以用单例实现相同的功能并不意味着它易于维护。通过使用IoC(至少这种带有构造函数的样式),您可以明确地声明对象具有的依赖关系。通过使用单例,信息隐藏在课堂中。它还使用替代实现替换这些依赖项变得更加困难。

因此,使用单例PluginManager,很难使用模拟插件测试您的HTTP服务器,而不是从磁盘上的某个位置查找它们。使用IoC版本,您可以传递IAppService的备用版本,它只是从预先填充的Dictionary中查找插件。

答案 2 :(得分:0)

虽然在这种情况下我仍然不确定IoC / DI是更好,但我确实看到了项目范围的好处。对于诸如日志记录和可配置性之类的事情,它肯定是正确的方法。

我期待在未来的项目中更多地尝试它。