我一直在阅读Ninject文档,我到达了它谈论工厂的部分(检查http://www.ninject.org/wiki.html或http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/)。在那里引用了Abstract Factory模式(维基百科)。
我一直发现维基百科文章中描述模式的方式与Ninject示例之间存在差异。我也搜索了SO并阅读了与该主题相关的一些答案,我仍然观察到与维基百科中描述的相似的相似之处。
你可以注意到:
我们有:
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();
而不是通过构造函数注入依赖项之外,我没有看到这一点。可能有一个用于能够使用这样的代码(示例?)但是它是否只有它呢?维基百科以外的示例是否真的遵循抽象工厂模式?
答案 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中没有预见到的线程安全问题)Abstract Factory
和Factory Method
在许多情况下都是如此。然而,所有GoF设计模式的概念今天仍然像以前一样重要。
对于各种创作的GoF模式,当编写Gang of Four书时,像Ninject这样的IoC Containers还没有在主流中广泛使用。 此外,90年代中期的大多数语言都没有垃圾收集 - 因此,依赖于其他语言的类(&#34;依赖类&#34;)必须同时管理分辨率和控制生命周期,依赖关系,这可能有助于解释为什么显性工厂在90年代比现在更常见。
以下是一些例子:
如果工厂仅用于抽象创建,和/或允许可配置策略来解析单个依赖关系,并且不需要特殊的依赖关系生命周期控制,则可以完全避免工厂,并且可以保留依赖关系到IoC容器建立。
e.g。在OP提供的Wiki示例中,是否构建WinFormsButton
或OSXButton
的策略(决定)可能是一个应用程序配置,它将在生命周期内修复申请程序。
GoF风格示例
对于Windows和OSX实现,需要ICanvas
和ICanvasFactory
接口,以及另外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>();
因此,我们只需要一个接口,加上两个具体的WindowsCanvas
和OSXCanvas
类。该策略将在IoC引导中解决(例如Ninject Module.Load
)
Ninject现在负责注入依赖类的ICanvas
实例的生命周期。
IoC取代抽象工厂
然而,在现代C#中仍然会出现一些类,其中一个类仍然需要一个依赖工厂,而不仅仅是一个注入的实例,例如。Screen
类可能允许动态添加多个按钮)IDisposable
)即便如此,使用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>();