6

介绍

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

我一直发现 Wikipedia 文章和 Ninject 示例中描述模式的方式之间存在差异。我还在 SO 上进行了搜索并阅读了一些与该主题相关的答案,但我仍然观察到与 Wikipedia 中描述的方式不同的相似之处。

细节

在维基百科中

类图 你可以注意到:

  • 抽象工厂多种实现*具体工厂* 。
  • 抽象产品多种实现*具体产品* 。
  • 每个混凝土工厂生产一种混凝土产品。在图中 WinFactory生成WinButtonOSXFactory生成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

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

问题

维基百科以外的例子真的遵循抽象工厂模式吗?

4

1 回答 1

3

TL;博士

维基百科以外的(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 模式,在编写 Gang of Four 书时,像 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.
    }
}

现代 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 Initialization patterns like Lazy

即便如此,使用 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");
                    }
                });
            });
    

抽象工厂可以替换为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);
    }
}

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

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

 case ButtonType.ExitButton:
      return KernelInstance.Get<OkButton>();
于 2017-03-12T11:02:23.717 回答