C# 设计模式 (1) Factory Method 模式
Notice: 本系列文章源自于O’Reilly的<C# 3.0 Design Patterns>一书。翻译为主,同时加上痘痘熊自个儿的幻想~章节并非按照书的顺序来,而是任性而为,想到哪儿就写到哪儿。:)
Before start…
开始之前,请确定你至少熟悉这几个主题:什么是设计模式?常见UML图例,以及c#语言(prefer 3.0)。
Role of Factory Method Pattern
简单的说,Factory模式是用来创建对象的一个模式。但是具体创建的是什么对象,交由其子类去决定。有点拗口是不?接下来,不同的子类可能实现同一个接口;而实现了Factory模式的方法负责初始化具体某一个子类,而判断的依据可以由程序逻辑决定。
Illustration
假设伦敦有一家高级商店全年都销售鳄梨(avocado)。它依赖于一个采购商保证鳄梨能一直定时的补货。这家采购商负责寻找最好的鳄梨并且供应给这家商店。这家采购商扮演的就是Factory Method的角色。想一想,它可以在不同的时间采购肯尼亚,南非,或者西班牙的鳄梨,因为每个地方出产鳄梨的季节都不一样。尽管这些水果都标着标签是从哪儿采购来的,可是对这家商店库管员来讲,它才懒得关心呢。下图显示了可能发生的实际情况:
Design
Client宣布需要一个Product,但是把实际的生成任务交给FactoryMethod去做。因此到底哪个特定的产品会被创建的决定被延迟到了FactoryMethod手里(原本应该是Client来做的事情)。UML图标如下:
这里的角色有:
IProduct
Product的接口
ProductA and ProductB
实现IProduct的类
Creator
提供FactoryMethod方法
这样的设计把具体产生哪个产品的决策集中在了同一个地方。如果客户知道所有的选项,那么所有可能的决策会散布在其代码的各个角落。然而client只关心获取到产品,它甚至不关心不同的子类间有任何的区别。当然,client争对不同的产品,也可能会有多个creator。
这是个自测题,自个儿做做吧,easy的很。
Implementation and Example: Avocado Sourcing
在这个例子当中,每个生产鳄梨的国家都实现了IProduct接口,这样它们就能为采购商供货。根据月份的不痛,FactoryMethod也会选择不同的供货商A(南非)或者B(西班牙)。注意加粗的那一行,client根本不知道到底是哪一个Product被create了(A或者B)。
using System;
using System.Collections;
class FactoryPattern {
// Factory Method Pattern Judith Bishop 2006
interface IProduct {
string ShipFrom( );
}
class ProductA : IProduct {
public String ShipFrom ( ) {
return " from South Africa";
}
}
class ProductB : IProduct {
public String ShipFrom ( ) {
return "from Spain";
}
}
class DefaultProduct : IProduct {
public String ShipFrom ( ) {
return "not available";
}
}
class Creator {
public IProduct FactoryMethod(int month) {
if (month >= 4 && month <=11)
return new ProductA( );
else
if (month == 1 || month == 2 || month == 12)
return new ProductB( );
else return new DefaultProduct( );
}
}
static void Main( ) {
Creator c = new Creator( );
IProduct product;
for (int i=1; i<=12; i++) {
product = c.FactoryMethod(i);
Console.WriteLine("Avocados "+product.ShipFrom( ));
}
}
}
/* Output
Avocados from Spain
Avocados from Spain
Avocados not available
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from South Africa
Avocados from Spain
*/
Use
Factory Method模式是实现应用独立的轻量级的模式。client定义好接口(这里是IProduct),然后交给模式去处理剩下的事情。
一个特别的好处是这个模式可以连接类似的类层次(connect parallel class hierarchies)。如果每一个层次都有一个FactoryMethod,那么它就能用更灵活的方式来创建子类的实例。
Use the Factory Method pattern when…
- 灵活性非常重要
- 对象可以被子类继承
- 某个特别的原因使得某个子类可能替代另外一个子类——逻辑变动是这个模式的一部分
- client在平行层次中委托责任给子类
可以考虑的替代方案:
- Abstract Factory, Prototype, or Builder patterns,这些更灵活,也更复杂
- Prototype 模式用来存储对象集合来从abstract 工厂中克隆(clone)
Further Thinking
让我们来看看这个模式在BlogEngine.NET中的应用。现在很多程序都支持多个数据源,比如同时可以安装到Sql Server, Oracle,甚至使用XML文件存储数据。这里有个很好听的名字叫做Provider。是不是很耳熟?刚才那个鳄梨,在西班牙,南非的销售商,不也是Provider?没错,一回事。还有什么MembershipProvider,什么RoleProvider,统统都大同小异。
BlogEngine中的Factory Method Pattern
我们首先来分析源码中那些class扮演的是哪些角色。
首先Client,自然就是BlogEngine本身了。
接下来IProduct,这里需要的Product是数据库的Providers,这些Providers需要共同实现Client定义好的接口。打开DbBlogProvider类,可以看到如下定义:
public class DbBlogProvider: BlogProvider
这里的BlogProvider定义如下:
public abstract class BlogProvider : ProviderBase
实际上这里BlogProvider扮演的角色就是IProduct的角色。也就是说,这是BlogEngine框架规定的必须实现的接口。(尽管它是个抽象类,因为实现了ProviderBase里面的方法)
OK,下一个是ProductA,也就是DbBlogProvider,负责用数据库作为存储时的数据源Provider。另外一个XmlBlogProvider类,是ProdcutB,也就是XML作为数据源时的Provider。不过有的朋友可能找不到XmlBlogProvider类,因为这个类散布在XmlProvider目录下的多个类当中,例如Setting.cs。
Next,Creator?Where is it?打开BlogService Class,定位到LoadPrividers方法:
/// <summary>
/// Load the providers from the web.config.
/// </summary>
private static void LoadProviders()
{
// Avoid claiming lock if providers are already loaded
if (_provider == null)
{
lock (_lock)
{
// Do this again to make sure _provider is still null
if (_provider == null)
{
// Get a reference to the <blogProvider> section
BlogProviderSection section = (BlogProviderSection)WebConfigurationManager.GetSection("BlogEngine/blogProvider");
// Load registered providers and point _provider
// to the default provider
_providers = new BlogProviderCollection();
ProvidersHelper.InstantiateProviders(section.Providers, _providers, typeof(BlogProvider));
_provider = _providers[section.DefaultProvider];
if (_provider == null)
throw new ProviderException("Unable to load default BlogProvider");
}
}
}
}
看注释就可以知道,这个函数其实就是我们的Creator,它装载web.config中的Providers,并且选择其中的默认Provider作为当前使用的,也就是真正产生的Product(DB或者XML Provider)。
现在,把上面UML图换成BlogEngine中的Class,是不是觉得一目了然了呢?Next……
PetShop 4.0中的Factory Method Pattern
这个就当作练习题吧,我也写不动了,累死了,战友们不妨按照上面的Role分别去找找相应的实现,会有收获的。 : )