MSD*_*MSD 6 .net oop ninject abstract-factory
我一直在阅读Ninject文档,我到达了有关工厂的部分(请查看http://www.ninject.org/wiki.html或http://www.planetgeek.ch/2011/12/31/ ninject-extensions-factory-introduction /).那里引用了抽象工厂模式(维基百科).
我一直发现维基百科文章中描述模式的方式与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();
}
Run Code Online (Sandbox Code Playgroud)
和
kernel.Bind<IBarFactory>().ToFactory();
Run Code Online (Sandbox Code Playgroud)
var bar = this.barFactory.CreateBar();而不是通过构造函数注入依赖项之外,我没有看到这一点.可能有一个用于能够使用这样的代码(例子?)但是它是否就是这样?除了维基百科以外的例子是否真的遵循抽象工厂模式?
长话短说
除了维基百科之外的(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 中未预见到的线程安全问题)Abstract Factory和也是如此。Factory Method然而,所有 GoF 设计模式的概念在今天仍然和以往一样重要。
对于各种创造性的 GoF 模式,当《四人帮》一书撰写时,像 Ninject 这样的 IoC 容器还没有在主流中广泛使用。此外,90 年代中期的大多数语言都没有垃圾收集 - 因此,依赖于其他类的类(“依赖类”)必须管理依赖项的解析并控制其生命周期,这可能有助于解释为什么显式工厂在 90 年代比今天更常见。
以下是一些示例:
如果工厂仅用于抽象创建,和/或允许可配置策略来解决单个依赖项,并且不需要特殊的依赖项生命周期控制,则可以完全避免工厂,并将依赖项留给 IoC容器来构建。
例如,在 OP 提供的 Wiki 示例中,是否构建 aWinFormsButton或 an的策略(决策)很可能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.
}
}
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)
因此,我们只需要一个接口,加上两个具体的WindowsCanvas和OSXCanvas类。该策略将在 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)