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

MSD*_*MSD 6 .net oop ninject abstract-factory

介绍

我一直在阅读Ninject文档,我到达了有关工厂的部分(请查看http://www.ninject.org/wiki.htmlhttp://www.planetgeek.ch/2011/12/31/ ninject-extensions-factory-introduction /).那里引用了抽象工厂模式(维基百科).

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

细节

在维基百科

类图 你可以注意到:

  • 多种实现*混凝土厂*抽象工厂.
  • 多个实现*Concrete Product*抽象产品.
  • 每个具体的工厂产生的混凝土产品.在图中,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();
}
Run Code Online (Sandbox Code Playgroud)

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

在SO

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

这个问题

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

Stu*_*tLC 3

长话短说

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

在概念上,是的,像 Ninject 这样的 IoC 容器(本着其精神)允许Abstract Factory的原始目标(以及更多) ,但在实现中,不,使用像 Ninject 这样的 IoC 容器的现代应用程序不需要无数的具体目标。工厂类 - 通常除了new()构建它们所针对的类型的具体实例之外什么也不做- 特别是在垃圾收集环境(如 JVM 和托管 .Net)中使用时。

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

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

基本原理

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

例如在 C# 中,

  • Iterator通常直接合并到编译器中 ( foreach / GetEnumerator())
  • Observer标配多播委托和事件等。
  • 使用Singleton,而不是使用静态实例,我们通常会使用 IoC 来管理单例。是否通过惰性实例来管理生命周期的决定完全是一个单独的问题。(我们Lazy<T>为此做好了准备,包括处理 GoF 中未预见到的线程安全问题)
  • 我相信,当 IoC 容器可用时,在许多情况下Abstract Factory和也是如此。Factory Method

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

对于各种创造性的 GoF 模式,当《四人帮》一书撰写时,像 Ninject 这样的 IoC 容器还没有在主流中广泛使用。此外,90 年代中期的大多数语言都没有垃圾收集 - 因此,依赖于其他类的类(“依赖类”)必须管理依赖项的解析并控制其生命周期,这可能有助于解释为什么显式工厂在 90 年代比今天更常见。

以下是一些示例:

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

例如,在 OP 提供的 Wiki 示例中,是否构建 aWinFormsButton或 an的策略(决策)很可能OSXButton是一个应用程序配置,该配置在应用程序进程的生命周期内是固定的。

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

public class Screen
{
    private readonly ICanvas _canvas;
    public Screen(ICanvas canvas)
    {
        _canvas = canvas;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后所需要做的就是在 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>();
Run Code Online (Sandbox Code Playgroud)

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

抽象工厂的IoC替换

然而,在现代 C# 中仍然存在一些情况,其中类仍然需要依赖项工厂,而不仅仅是注入实例,例如

  • 当要创建的实例数量未知/动态确定时(例如,一个Screen类可能允许动态添加多个按钮)
  • 当依赖类不应该具有延长的生命周期时 - 例如,释放所创建的依赖项拥有的任何资源很重要(例如依赖项实现IDisposable
  • 当依赖实例的创建成本很高,并且实际上可能根本不需要时 - 请参阅延迟初始化模式,例如延迟

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

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

    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");
                    }
                });
            });
    
    Run Code Online (Sandbox Code Playgroud)

抽象工厂可以替换为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);
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然在上面,我们简单地使用了 anenum来抽象创建策略,但 IoC 框架允许以多种方式指定具体创建“区分”的抽象,例如通过命名抽象、通过属性(不推荐 -这会污染依赖代码),绑定到上下文例如通过检查其他参数,或依赖类类型等。

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

 case ButtonType.ExitButton:
      return KernelInstance.Get<OkButton>();
Run Code Online (Sandbox Code Playgroud)