.net - Ninject, "Abstract Factory"模式和运行时条件解析

标签 .net oop ninject abstract-factory

介绍

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

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

细节

在维基百科

您会注意到:

  • 多个实现*抽象工厂的* Concrete Factory *。
  • 多种实现*抽象产品的* Concrete Product *。
  • 每个混凝土工厂都会生产混凝土产品。在该图中,WinFactory生成WinButton,而OSXFactory生成OSXButton
  • 如果我要编写一个在运行时有条件确定类型的程序,则很明显,我将具有一个通用抽象的多个实现(在该图中,Button接口(interface)的多个实现)
  • 如果我要使用Abstract Factory模式来实现这一点,那么根据Wikipedia的文章,我推断至少一种方法_该文章未显示另一种方法_是有条件地解析为中的一种工厂的多个实现,这反过来又会给我产品
  • 多个实现之一

    在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();这样的代码,而不是通过构造函数注入(inject)依赖项之外,我看不到这一点。能够使用像这样的代码(示例?)可能会有用,但仅此而已吗?

  • 如此
  • 我看到了this。检查最后一条注释,该注释似乎表明工厂中有多种方法返回不同的实现,但是我们仍然仅使用一个具体工厂,因此未关注Wikipedia文章
  • This似乎类似于Ninject的示例
  • this中使用了一个依赖项,但是类型仍然不是抽象的

  • 问题

    除Wikipedia之外的其他示例是否真的遵循了“抽象工厂”模式?

    最佳答案

    TL; DR

    Are the (Ninject) examples other than the Wikipedia one really following the Abstract Factory pattern?



    从概念上讲,是的,像Ninject这样的IoC容器允许(本着)本来是Abstract Factory的原始目标(和更多),但是在实现中,不,使用像Ninject这样的IoC容器的现代应用程序不需要大量的混凝土。工厂类-通常只对new()为其构建类型的具体实例进行其他操作-尤其是在JVM和托管.Net等垃圾收集环境中使用时。

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

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

    基本原理

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

    例如在C#中,
  • Iterator通常直接合并到编译器中(foreach / GetEnumerator())
  • Observer是多播委托(delegate)和事件等的标准配置。
  • 使用Singleton,而不是使用静态实例化,我们通常将使用IoC来管理单例。是否通过延迟实例化来管理生命周期的决定将完全是另外一个问题。 (我们为此使用了Lazy<T>,包括处理GoF中未预见的线程安全问题)
  • 我相信在IoC容器可用的情况下,对于Abstract FactoryFactory Method在许多情况下都是如此。

  • 但是,所有GoF设计模式的概念今天仍然像以往一样重要。

    对于各种创新的GoF模式,当编写《四人帮》一书时,诸如Ninject之类的IoC容器尚未在主流中广泛使用。
    同样,90年代中期的大多数语言都没有垃圾回收-结果,依赖于其他语言的类(“Dependent classes”)必须管理依赖关系的分辨率并控制其生命周期,这可能有助于解释为什么显式工厂在90年代比现在更为普遍。

    这里是一些示例:

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

    例如在OP提供的Wiki示例中,构建WinFormsButtonOSXButton的策略(决策)可能是应用程序配置,在应用程序生命周期内是固定的。

    GoF样式示例

    对于Windows和OSX实现,将需要ICanvasICanvasFactory接口(interface),以及另外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 bootstrap 中进行配置:
    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>();
    

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

    抽象工厂的IoC替换

    但是,在现代C#中仍然存在一些情况,其中类仍然需要依赖工厂,而不仅仅是注入(inject)实例,例如
  • 当要创建的实例数未知/动态确定时(例如Screen类可以允许动态添加多个按钮)
  • 依赖项类不应延长生命周期-例如释放所创建的依赖项所拥有的任何资源(例如,该依赖项实现IDisposable)的重要位置
  • 如果依赖项实例创建起来既昂贵又可能根本不需要,请参阅懒惰的初始化模式,例如Lazy

  • 即便如此,使用IoC容器还是可以避免多个工厂类别的泛滥。
  • 摘要工厂接口(interface)(例如Wiki示例中的GUIFactory)可以简化为使用lambdas Func<discriminants, TReturn>-即因为Factory通常只有一个公共(public)方法Create(),因此无需构建工厂接口(interface)或具体类。例如
    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>();
    

    关于.net - Ninject, "Abstract Factory"模式和运行时条件解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21285090/

    相关文章:

    c# - 我的webapi项目还依赖数据访问层吗?

    .net - 是否有 WPF 控件可用于展开/折叠面板(动画)

    .net - VS 2012 在启动时创建新文件夹

    c# - NinjectModule 的加载顺序

    java - 观察者设计模式的主题是接口(interface)还是父类(super class)?

    java - 为什么我们需要 super 关键字来调用默认接口(interface)方法?

    c# - 哪个是测试 Ninject 绑定(bind)的好方法?

    .net - NHibernate调用PostgresSQL的SELECT DISTINCT ON()

    c# - System.Threading.Thread 在 .NET Core 中的含义与在 .NET Framework 中的含义相同吗?

    只包含常量的 PHP 类