Ninject,“抽象工厂”模式和运行时条件解析

时间:2014-01-22 14:04:43

标签: .net oop ninject abstract-factory

简介

我一直在阅读Ninject文档,我到达了它谈论工厂的部分(检查http://www.ninject.org/wiki.htmlhttp://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/)。在那里引用了Abstract Factory模式(维基百科)。

我一直发现维基百科文章中描述模式的方式与Ninject示例之间存在差异。我也搜索了SO并阅读了与该主题相关的一些答案,我仍然观察到与维基百科中描述的相似的相似之处。

详细

在维基百科

Class Diagram 你可以注意到:

  • 抽象工厂
  • 多个实施 * Concrete Factory *
  • 抽象产品的多个实施 *具体产品*
  • 每个具体工厂生成具体产品。在图中 WinFactory 生成 WinButton 并且 OSXFactory 生成 OSXButton
  • 如果我要编写一个在运行时确定类型有条件的程序,很明显我会有一个共同抽象的多个实现(在图中,Button接口的多个实现)
  • 如果我要使用抽象工厂模式来实现这一点,那么根据维基百科的文章我推断至少有一种方式_ 文章没有显示另一种方法它 _将有条件地解析为工厂的多个实现之一,这反过来会给我一个多个实现产品

在Ninject文档中

我们有:

public class Foo
{
    readonly IBarFactory barFactory;

    public Foo(IBarFactory barFactory)
    {
        this.barFactory = barFactory;
    }

    public void Do()
    {
        var bar = this.barFactory.CreateBar();
        ...
    }
}

public interface IBarFactory
{
    Bar CreateBar();
}

kernel.Bind<IBarFactory>().ToFactory();
  • 我没有看到工厂和产品的多个实现
  • 除了允许这样的代码var bar = this.barFactory.CreateBar();而不是通过构造函数注入依赖项之外,我没有看到这一点。可能有一个用于能够使用这样的代码(示例?)但是它是否只有它呢?

在SO

  • 我看到了this。检查最后一条评论似乎表明工厂内有多种方法返回不同的实现,但我们仍然只使用一个 具体工厂,因此不遵循维基百科文章
  • This似乎与Ninject的例子类似
  • this中使用了一个依赖项,但该类型仍然不是抽象的

问题

维基百科以外的示例是否真的遵循抽象工厂模式?

1 个答案:

答案 0 :(得分:3)

<强> TL; DR

  
    

除了维基百科之外的(Ninject)示例是否真的遵循抽象工厂模式?

  

概念上,是的,像Ninject这样的IoC容器允许(在本质上)抽象工厂的原始目标(以及更多),但在实现中,不是,使用IoC容器的现代应用程序像Ninject一样,并不需要无数的具体工厂类 - 除了new()它们所构建的类型的具体实例之外什么都不做 - 特别是在JVM等垃圾收集环境中使用时管理.Net。

IoC容器具有反射,工厂函数/ lambda,甚至动态语言等工具来创建具体类。这包括允许其他创建策略,例如允许区分参数和调用的上下文。

我建议不要关注GoF模式的原始代码类实现,而是建议关注每个GoF模式的高级概念,以及每个模式要解决的问题。

<强>原理

许多四人组模式(如Abstract Factory)经常被现代语言和框架所吸收,或者可以简化 - 即自1990年代中期以来的进化语言和设计改进许多实例意味着核心GoF模式概念可以更简洁地实现,并且在某些情况下可能使原始GoF书中的几个代码和UML类冗余。

e.g。在C#,

  • Iterator经常直接纳入编译器(foreach / GetEnumerator()
  • Observer标配多播委托和事件等。
  • 使用Singleton,而不是使用静态实例,我们通常会使用IoC来管理单例。是否用懒惰的实例来管理生命周期的决定将完全是一个单独的问题。 (我们有Lazy<T>,包括处理GoF中没有预见到的线程安全问题)
  • 我相信在{I}容器可用时Abstract FactoryFactory Method在许多情况下都是如此。

然而,所有GoF设计模式的概念今天仍然像以前一样重要。

对于各种创作的GoF模式,当编写Gang of Four书时,像Ninject这样的IoC Containers还没有在主流中广泛使用。 此外,90年代中期的大多数语言都没有垃圾收集 - 因此,依赖于其他语言的类(&#34;依赖类&#34;)必须同时管理分辨率和控制生命周期,依赖关系,这可能有助于解释为什么显性工厂在90年代比现在更常见。

以下是一些例子:

如果工厂仅用于抽象创建,和/或允许可配置策略来解析单个依赖关系,并且不需要特殊的依赖关系生命周期控制,则可以完全避免工厂,并且可以保留依赖关系到IoC容器建立。

e.g。在OP提供的Wiki示例中,是否构建WinFormsButtonOSXButton的策略(决定)可能是一个应用程序配置,它将在生命周期内修复申请程序。

GoF风格示例

对于Windows和OSX实现,需要ICanvasICanvasFactory接口,以及另外4个类 - OSX和Windows Canvasses,以及两者的FactoryClasses。战略问题,即CanvasFactory要解决的问题也需要解决。

public class Screen
{
    private readonly ICanvas _canvas;
    public Screen(ICanvasFactory canvasFactory)
    {
        _canvas = canvasFactory.Create();
    }

    public ~Screen()
    {
        // Potentially release _canvas resources here.
    }
}

现代IoC时代简单工厂方法示例

如果不需要在运行时动态确定具体类的决定,则可以完全避免工厂。依赖类可以简单地接受依赖抽象的实例。

public class Screen
{
    private readonly ICanvas _canvas;
    public Screen(ICanvas canvas)
    {
        _canvas = canvas;
    }
}

然后所需要的只是在IoC引导中配置它:

if (config.Platform == "Windows")
    // Instancing can also be controlled here, e.g. Singleton, per Request, per Thread, etc
    kernel.Bind<ICanvas>().To<WindowsCanvas>(); 
else
    kernel.Bind<ICanvas>().To<OSXCanvas>();

因此,我们只需要一个接口,加上两个具体的WindowsCanvasOSXCanvas类。该策略将在IoC引导中解决(例如Ninject Module.Load) Ninject现在负责注入依赖类的ICanvas实例的生命周期。

IoC取代抽象工厂

然而,在现代C#中仍然会出现一些类,其中一个类仍然需要一个依赖工厂,而不仅仅是一个注入的实例,例如。

  • 当要创建的实例数未知/动态确定时(例如Screen类可能允许动态添加多个按钮)
  • 当依赖类不应该延长寿命时 - 例如释放由创建的依赖项所拥有的任何资源(例如,依赖项实现IDisposable
  • 非常重要
  • 当依赖项实例的创建成本高昂时,实际上可能根本不需要 - 请参阅惰性初始化模式,如Lazy

即便如此,使用IoC容器也有简化,可以避免多个工厂类的扩散。

  • 抽象工厂接口(例如Wiki示例中的GUIFactory)可以简化为使用lambdas Func<discriminants, TReturn> - 即因为Factory通常只有一个公共方法Create(),所以无需构建工厂接口或具体类。 e.g。

    Bind<Func<ButtonType, IButton>>()
        .ToMethod(
            context =>
            {
                return (buttonType =>
                {
                    switch (buttonType)
                    {
                        case ButtonType.OKButton:
                            return new OkButton();
                        case ButtonType.CancelButton:
                            return new CancelButton();
                        case ButtonType.ExitButton:
                            return new ExitButton();
                        default:
                            throw new ArgumentException("buttonType");
                    }
                });
            });
    

可以使用Func resolver

替换抽象工厂
public class Screen
{
    private readonly Func<ButtonType, IButton> _buttonResolver;
    private readonly IList<IButton> _buttons;
    public Screen(Func<ButtonType, IButton> buttonResolver)
    {
        _buttonResolver = buttonResolver;
        _buttons = new List<IButton>();
    }

    public void AddButton(ButtonType type)
    {
        // Type is an abstraction assisting the resolver to determine the concrete type
        var newButton = _buttonResolver(type);
        _buttons.Add(newButton);
    }
}

虽然在上面,我们只是使用enum来抽象创建策略,但IoC框架允许抽象出具体的创作和歧视。以多种方式指定,例如通过命名抽象,通过属性(不建议 - 这污染依赖代码),绑定到context,例如通过检查其他参数,或者依赖类类型等

同样值得注意的是,IoC容器也可以在依赖性的时候提供帮助,因为它还具有需要解决的进一步依赖性(可能再次使用抽象)。在这种情况下,可以避免new,并且通过容器再次解析每个按钮类型的构建。例如上述引导代码也可以指定为:

 case ButtonType.ExitButton:
      return KernelInstance.Get<OkButton>();