介绍
我一直在阅读Ninject文档,到达了有关工厂的部分(请检查http://www.ninject.org/wiki.html或http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/)。那里引用了Abstract Factory模式(维基百科)。
我一直在Wikipedia文章中描述模式的方式与Ninject示例之间存在差异。我还搜索了SO,并阅读了与此主题相关的一些答案,但在与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)依赖项之外,我看不到这一点。能够使用像这样的代码(示例?)可能会有用,但仅此而已吗? 如此
问题
除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 Factory
和Factory Method
在许多情况下都是如此。
但是,所有GoF设计模式的概念今天仍然像以往一样重要。
对于各种创新的GoF模式,当编写《四人帮》一书时,诸如Ninject之类的IoC容器尚未在主流中广泛使用。
同样,90年代中期的大多数语言都没有垃圾回收-结果,依赖于其他语言的类(“Dependent classes”)必须管理依赖关系的分辨率并控制其生命周期,这可能有助于解释为什么显式工厂在90年代比现在更为普遍。
这里是一些示例:
如果仅使用工厂来抽象创建内容,并且/或者允许使用可配置的策略来解决单个依赖项,并且在不需要特殊的依赖项生命周期控制的情况下,则可以完全避免工厂,并且可以将依赖项留给IoC容器来建立。
例如在OP提供的Wiki示例中,构建WinFormsButton
或OSXButton
的策略(决策)可能是应用程序配置,在应用程序生命周期内是固定的。
GoF样式示例
对于Windows和OSX实现,将需要ICanvas
和ICanvasFactory
接口(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),再加上两个具体的WindowsCanvas
和OSXCanvas
类。该策略将在IoC引导中解决(例如NinjectModule.Load
)
Ninject现在负责注入(inject)到依赖类中的ICanvas
实例的生命周期。
抽象工厂的IoC替换
但是,在现代C#中仍然存在一些情况,其中类仍然需要依赖工厂,而不仅仅是注入(inject)实例,例如当要创建的实例数未知/动态确定时(例如 Screen
类可以允许动态添加多个按钮)依赖项类不应延长生命周期-例如释放所创建的依赖项所拥有的任何资源(例如,该依赖项实现 IDisposable
)的重要位置如果依赖项实例创建起来既昂贵又可能根本不需要,请参阅懒惰的初始化模式,例如Lazy
即便如此,使用IoC容器还是可以避免多个工厂类别的泛滥。摘要工厂接口(interface)(例如Wiki示例中的 GUIFactory
)可以简化为使用lambdasFunc<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/