设计模式
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第5章抽象工厂模式

5.1 经典回顾

5.2 按计划实施生产

5.3 定义计划与生产间的映射关系

5.4配置生产计划

5.5 基于Delegate的生产外包

5.6 小结

Provide an interface for creating families of related or dependent objects.

Design Patterns : Elements of Reusable Object-Oriented Software

在介绍抽象工厂模式前,我们先回顾之前的简单工厂模式和工厂方法模式,它们的核心目标都是一致的:直接由客户程序创建对象的时候,由于目标对象本身可能会经常变化,但它们的抽象能力却相对固定,因此我们需要通过工厂把这个创建过程封装出来,让客户程序不需自己new()目标类型。

简单工厂方法的职责非常简单:构造某个实体类型,然后把实例作为抽象类型返回;工厂方法则进一步抽象出一个抽象的创建者和一个抽象的产品类型,而实际的执行过程是具体工厂创建具体的产品类型,不过,因为具体工厂和具体产品类型都可以被抽象为之前定义的抽象创建者和抽象产品类型,因此即便面对一个很庞大的具有复杂家族关系的类型系统,客户程序在操作的过程中也可以基于抽象创建者获得满足某种抽象类型的产品实例。

回到我们的项目实际,有时候我们要创建的不是继承自“一个”抽象类型的产品,它们本身就是多个具有一定依赖关系,但非同源的类型,因此,需要提供另一种形式的工厂,它可以产生“一系列”具有相关依赖关系的类型。

这里的同源其实是有一定语意范围的,因为无论是.NET还是Java语言都可以追溯到object类型,很可能为了整体系统升级,您的应用有自己的最抽象类型,一个OA项目可能被您命名为AutomationObjectBase。不过这里的“同源”指的是在某个语意范围内,您将它们定义为不同的类型,比如数据访问中有Connection、Adapter、Command、Prameter等对象,虽然它们都继承自object,但在进行数据访问这个语意环境下,我们认为它们是不同类的,非“同源”的。

我们之前在讨论“批量工厂”的时候提过,简单工厂模型并不像现实中的工厂;其实工厂方法也不像现实中的很多企业,比如某个中式连锁快餐店不仅提供炸酱面,也提供酸梅汤,而一个酒店中餐部也提供类似的中餐,只不过口味不同,只提供阳春面和鲜榨果汁。从抽象角度看,它们都是某种工厂,只不过生产的是一系列产品而已。如果采用类似工厂方法模式完成这个工作,我们可以定义出INoodle和IDrink,然后再实现[I 阳春面]、[I 炸酱面]之类的不同类型,并设计不同的具体工厂,最后客户程序调用INoodleFactory和IDrinkFactory获得具体的食物。这个思路是可以实现的,不过客户程序有点麻烦。

我们可以变个思路,抽象出一个接口IRestaurant,它可以生产INoodle和IDrink,客户程序面对的是一个个“店”,通过这个“店”获得自己需要的各种食品,而不是让客户程序自己出去收集不同类食品(有点怪怪的,感觉上就像自己打猎)。

5.1 经典回顾

《设计模式:可复用面向对象软件的基础》中对抽象工厂的意图描述得特别简练——“提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类型”。我们来解读一下这个意图:

● 可以返回一系列相关或相互依赖对象的接口。

● 需要指定一个接口,也就是抽象的工厂,它的接口定义中包括返回那些相关对象接口的方法定义。

这样我们就可以大概看清抽象工厂的静态模型,如图5-1所示。

图5-1 经典抽象工厂模式的静态结构

首先,把一系列客户程序需要的对象定义为抽象的产品IProductA和IProductB,而实体类型则实现自这两个抽象类型。然后把能够产生这一系列抽象产品的工厂定义为抽象工厂IAbstractFactory,通过它的抽象方法可以分别获得每种抽象类型。最后,定义一系列实体工厂类型,它们按照抽象工厂的定义,分别实现自己如何返回这一系列抽象类型。

上面的类结构其实是一种相对理想的状态,ConcreteFactory1 分别调用ProductA1和ProductB1,而ConcreteFactory2分别调用ProudctA2和ProductB2,但实际项目中往往不是这种完全排他的方式,例如IProductB只有一个实体类ProductB,因此ConcreteFactory1和ConcreteFactory2在CreateProductB方法中实际调用的都是ProductB。

那么抽象工厂一般适用于哪些情景呢?

● 如果您的客户程序希望将业务逻辑和产品创建相分离,把一系列产品的创建,甚至一些系列中某些产品的组合全部放在抽象工厂内部解决。比如:上面的例子中除了可以把INoodle、IDrink创建出来外,还可以创建出套餐ISetLunch,至于套餐里面是怎么组合的,IAbstractFactory不知道,客户程序也不知道,Concrete Factory安排即可。(项目中这种设计对象组合的内容,往往会借助于下面介绍的Builder模式联合实现。)

● 当你要强调一系列相关的产品对象的设计,便于客户程序集中调用时。

● 当然,您也可以让抽象工厂“大材小用”,为了简化创建部分的接口,即便客户程序当前只需要某一类产品,为了省去以后再去把那些可以预见到的相关类型维护到类库里的工作量,先把它们加到一个抽象工厂里面。

但同时您也要看到经典抽象工厂模式的硬伤,即它不适合实体类型快速变化的情况。例如使用CreateProductA()和CreateProductB()分别生成了IProductA和IProductB,但如果需要增加一个“相关或相互依赖”的IProudctC,该怎么办呢?后面我们会讨论如何通过泛型、委托、反射和配置等机制弥补经典抽象工厂模式的不足,使我们在项目中更灵活稳健地使用经典抽象工厂模式。下面我们先给出抽象工厂模式的经典实现,示例代码如下:

C#

/// 定义不同类型的抽象产品
public interface IProductA { }
public interface IProductB { }
/// 定义抽象工厂
public interface IAbstractFactory
{
    IProductA CreateProducctA();
    IProductB CreateProducctB();
}
/// 定义实体产品
public class ProductA1 : IProductA { }
public class ProductA2X : IProductA { }
public class ProductA2Y : IProductA { }
public class ProductB1 : IProductB { }
public class ProductB2 : IProductB { }
///实体工厂
public class ConcreteFactory1 : IAbstractFactory
{
    public virtual IProductA CreateProducctA(){return new ProductA1();}
    public virtual IProductB CreateProducctB() { return new ProductB1(); }
}
public class ConcreteFactory2 : IAbstractFactory
{
    public virtual IProductA CreateProducctA() { return new ProductA2Y(); }
    public virtual IProductB CreateProducctB() { return new ProductB2(); }
}

Unit Test

[TestMethod]
public void Test()
{
    IAbstractFactory factory=new ConcreteFactory2();
    IProductA productA=factory.CreateProducctA();
    IProductB productB=factory.CreateProducctB();
    Assert.AreEqual<Type>(typeof(ProductA2Y), productA.GetType());
    Assert.AreEqual<Type>(typeof(ProductB2), productB.GetType());
}

相信您在看单元测试的时候会发现一个问题,就像前面章节所说的问题一样,虽然客户端依赖抽象的IAbstractFactory,但为了获得IAbstractFactory,需要new ConcreteFactory2(),逻辑上和具体的工厂类型耦合在一起。“依赖注入”非常适于解决这个问题,比如您可以把ConcreteFactory2写在配置文件里,通过读取配置文件动态构造出IAbstractFactory。

5.2 按计划实施生产

5.2.1为抽象工厂补充类型映射器

上面的示例中每个Concrete Factory执行的内容都是固定的,即依次new()出每类对象,这几乎就是foreach 过程。如果我们之前把一批需要做好的任务一次性地告诉某个实体IAbstractFractory类型,是不是就可以把这个过程自动化呢?示例代码如下:

C#

public class ConcreteFactory : IAbstractFactory
{
    private Type typeA;
    private Type typeB;
    public ConcreteFactory(Type typeA, Type typeB)
    {
        this.typeA=typeA;
        this.typeB=typeB;
    }
    public virtual IProductA CreateProducctA()
    {
        return (IProductA)Activator.CreateInstance(typeA);
    }
    public virtual IProductB CreateProducctB()
    {
        return (IProductB)Activator.CreateInstance(typeB);
    }
}

您也许会马上指出,这样是错误的,因为客户程序不仅要关心 ConcreteFactory,还要关心ConcreteProduct,这是绝对不能允许的。的确是这样。不过,如果typeA和typeB是通过配置为文件,经由ConfigurationManager获得的,就可以了。

我们发现CreateProductA()和CretaeProductB()的方法实在太像了,反复做这种Ctrl-C &&Ctrl-V的工作实在没意思,那我们就做些修改,其代码如下:

C#

public interface IAbstractFactory
{
    T Create<T>() where T : class;
}
public abstract class AbstractFactoryBase : IAbstractFactory
{
    // 外部机制通过配置获取的“抽象类型/实体类型”映射字典
    protected IDictionary<Type, Type> mapper;
    public AbstractFactoryBase(IDictionary<Type, Type> mapper)
    //构造函数注入
    {
        this.mapper=mapper;
    }
    public virtual T Create<T>() where T : class
    {
        if ((mapper == null) || (mapper.Count == 0)||
          (!mapper.ContainsKey(typeof(T))))
          throw new ArgumentException("T");
        Type targetType=mapper[typeof(T)];
        return (T)Activator.CreateInstance(targetType);
    }
}
public class ConcreteFactory : AbstractFactoryBase
{
    public ConcreteFactory(IDictionary<Type, Type> mapper) : base(mapper) { }
}

Unit Test

IAbstractFactory AssemblyFactory()
{
    // 生成映射关系字典,并将字典注入到IAbstractFactory中,实际项目中通过读取配置文件
      完成
    IDictionary<Type, Type> dictionary=new Dictionary<Type, Type>();
    dictionary.Add(typeof(IProductA), typeof(ProductA1));
    dictionary.Add(typeof(IProductB), typeof(ProductB1));
    return new ConcreteFactory(dictionary);
}
[TestMethod]
public void Test()
{
    IAbstractFactory factory=AssemblyFactory();
    IProductA productA=factory.Create<IProductA>();
    IProductB productB=factory.Create<IProductB>();
    // … …
}

通过这个改进,估计您也感觉到了,其实经典工厂模式可以通过很多方式改进,一个最直接的办法就是增加一个“Concrete Product/Abstract Product”的字典对象(这里称之为TypeMapper,而每个“抽象类型/实体类型”对应关系则被称为TypePair),至于这个字典对象是通过配置文件、数据库还是其他什么方式建立的,IAbstractFactory和相关的Concrete Factory不需要了解。此方法如图5-2所示。

图5-2 增加了TypeMapper的抽象工厂模式的静态结构

5.2.2 处理模式的硬伤

回过头来看看之前提到的经典抽象工厂模式的“硬伤”,如果不对经典实现中的不同类型产品“一事一议”的方式做修改,当把IProductC加入时,您想想将发生什么情况?示例代码如下:

C# 添加新的IProductC

public class ProductC2 : IProductC { }

C# 修正IAbstractFactory

public interface IAbstractFactory
{
    IProductA CreateProducctA();
    IProductB CreateProducctB();
    IProductC CreateProducctC();   // 新增
}

C# 修正每个ConcreteFactory

public class ConcreteFactory1 : IAbstractFactory
{
    /// CreateProducctA() , CreateProducctB() … …
    public virtual IProductC CreateProducctC() { return new ProductC1(); }
}
public class ConcreteFactory2 : IAbstractFactory
{
    /// CreateProducctA() , CreateProducctB() … ..
    public virtual IProductC CreateProducctC() { return new ProductC2(); }
}

几乎就是牵一发而动全身,其原因是IAbstractFactory把它在定义中可以提供的方法“僵硬”了。但这个“僵硬”并非完全不好,毕竟它可以告诉其子类必须要实现哪些工作,尤其当不同子类的构造并不是简单到几乎new()一下即可的时候,这个相对“僵硬”的设计反而表现得不错。用泛型方法的实现相当于通过泛型参数告诉加工方法需要返回的抽象类型,这种方法虽然通用,而且在增加IProductC、IProductD的时候不需要做进一步修改,但它也有局限性,原因就是它仅适于通用的流水化作业方式,对于每种类型都按照既定的处理方式执行(这也可以通过插入不同的构造算法,进一步分化构造过程来实现)。通过TypeMapper来处理这个问题,不仅适于后面的那个基于泛型的实现,对于经典实现一样适用。

5.3 定义计划与生产间的映射关系

5.3.1 分析

在上面的分析中对某一个IAbstractFactory的处理提供了解决办法,可是项目中IAbstractFactory往往不只一个,毕竟几百个甚至上万个类的应用经常可以碰到,它们中发现“相关或相互依赖”的往往不只是个位数,因此 IAbstractFactory 也不会只有单独的一个,那么,如何提供一个更通用的解决办法呢?其实并不麻烦,将Assembler 提升为集中化的AssemblerMechaism,变每个Assembler支持一个IAbstractFactory为AssemblerMechanism支持一套TypeMapper/IAbstractFactory字典即可,如图5-3所示。

图5-3 进一步增加了装配机制的抽象工厂

与前面讨论的通过TypeMapper实现的泛型抽象工厂及之前经典抽象工厂不同相类似,通过TypeMapperDictionary实现一组IAbstract与TypeMapper之间的管理同样有其很明显的优势和局限。

● 优势:大大降低类库的编码量,应用中几个主要领域的多组“一系列相关或相互依赖”的类型都可以套用样板快速完成。工程中推荐TypeMapperDictionary通过配置解决,通过访问配置文件一次性(或Lazy方式)将须要使用的TypeMapper/IAbstractFactory读取到TypeMapperDictionary里;实际开发中TypePair、TypeMapper、TypeMapperDictionary可以直接用泛型集合替代,无需定制。

● 局限:通用性太强的问题容易导致对个性问题的处理不够理想,例如“一系列”的类型中就那么一两个类型构造过程除了new()之外,还有很多麻烦的处理,此时就只好退回到经典抽象工厂模式中“一事一议”地堆积 CreateProductA()、CreateProductB()……的方式。

下面看看在这种方式下批量产出应用需要的抽象工厂,现在我们要面对的是一个不算“庞大”的目标产品类型体系,其中X系列和Y系列各是一组“相关或具有依赖关系”的产品,如图5-4所示。

图5-4 示例的目标对象系统

5.3.2 登记映射关系

登记映射关系的示例代码如下:

C#

public abstract class TypeMapperBase : Dictionary<Type, Type> { }
public class TypeMapperDictionary : Dictionary<Type, TypeMapperBase> { }

5.3.3 用TypeMapper协助工厂生产

示例代码如下:

C#

public interface IAbstractFactory
{
    T Create<T>();
}
/// 增加了TypeMapper的IAbstractFactory
public interface IAbstarctFactoryWithTypeMapper : IAbstractFactory
{
    TypeMapperBase Mapper { get;set;}
}
public abstract class AbstractFactoryBase : IAbstarctFactoryWithTypeMapper
{
    protected TypeMapperBase mapper;
    public virtual TypeMapperBase Mapper
    {
        get { return mapper; }
        set { mapper=value; }
    }
    public virtual T Create<T>()
    {
        Type targetType=mapper[typeof(T)];
        return (T)Activator.CreateInstance(targetType);
    }
}

5.3.4 定义实体TypeMapper和实体工厂

示例代码如下:

C#

/// X系列和Y系列产品的具体TypeMapper类
public class ConcreteXTypeMapper : TypeMapperBase
{
    public ConcreteXTypeMapper()
    {
        base.Add(typeof(IProductXA), typeof(ProductXA2));
        base.Add(typeof(IProductXB), typeof(ProductXB1));
    }
}
public class ConcreteYTypeMapper : TypeMapperBase
{
    public ConcreteYTypeMapper()
    {
        base.Add(typeof(IProductYA), typeof(ProductYA1));
        base.Add(typeof(IProductYB), typeof(ProductYB1));
        base.Add(typeof(IProductYC), typeof(ProductYC1));
    }
}
public class ConcreteFactoryX : AbstractFactoryBase { }
public class ConcreteFactoryY : AbstractFactoryBase { }

5.3.5实现装配机制

把分散的Assembler 集中到一个统一的AssemblerMechanism之后,由于它自己维护一个TypeMapperDictionary 字典,因此它知道(通过配置或外部的注册机制)该把哪个TypeMapper实体注入到相应的IAbstractFactory中,示例代码如下:

C#

public static class AssemblyMechanism
{
    private static TypeMapperDictionary dictionary =
        new TypeMapperDictionary();
    /// 加载相关TypeMapper/IAbstractFactory的对应信息,实际项目中可以通过访问配
        置完成
    static AssemblyMechanism()
    {
        dictionary.Add(typeof(ConcreteFactoryX), new ConcreteXTypeMapper());
        dictionary.Add(typeof(ConcreteFactoryY), new ConcreteYTypeMapper());
    }
    ///为AbstractFactory找到它的TypeMapper,并注入
    public static void Assembly(IAbstarctFactoryWithTypeMapper factory)
    {
        TypeMapperBase mapper=dictionary[factory.GetType()];
        factory.Mapper=mapper;
    }
}

Unit Test

[TestMethod]
public void Test()
{
    IAbstarctFactoryWithTypeMapper factory=new ConcreteFactoryX();
    AssemblyMechanism.Assembly(factory);   // 绑定TypeMapper
    IProductXB productXB=factory.Create<IProductXB>();
    Assert.AreEqual<Type>(typeof(ProductXB1), productXB.GetType());
    // 测试另一个Abstract Factory
    factory=new ConcreteFactoryY();
    AssemblyMechanism.Assembly(factory);
    IProductYC productYC=factory.Create<IProductYC>();
    Assert.AreEqual<Type>(typeof(ProductYC1), productYC.GetType());
}

通过上面的示例我们可以看到一套 AssemblyMechanism、TypeMapper 组成框架,可以在项目中快速“量产”一批IAbstractFactory,可以为项目提供一批具有“相关或相互依赖关系”产品的new()替代品。但是,实现上还需要一些机制帮忙,比如AssemblyMechanism如何知道Concrete TypeMapper与Concrete抽象工厂的配置关系?每个Concrete TypeMapper是否可以不通过硬编码而通过外部机制完成自己TypePair的注册呢?

基于配置的Abstract Factory是个很好的选择。

5.4配置生产计划

先回顾一下:《设计模式:可复用面向对象软件的基础》中主要介绍抽象工厂的概念性实现,它告诉我们通过抽象工厂可以隔绝客户程序和一组“相关或具有依赖关系”对象(包括它们间的组合对象)的创建关系,它的工作其实也是解决耦合问题。这是个意图,也是我们需要实现的一个目标,但如果不借助第三方机制,是否能真正解决这些问题呢?客户程序虽然可以通过IAbstractFactory获得这些“相关”对象,但之前谁给它new ConcreteFactory呢?如果自己new,那么Concrete Factory关联的一组Concrete Product,它本身虽然不一定频繁修改,但很有可能需要频繁编译,对应的客户程序也不一定可以幸免。这是一个连锁反应。

那么怎么打破这个依赖的链条呢?抽象工厂要负责加工一类商品,某种程度上它与具体产品类型的关联要比工厂方法定义的耦合性强,链条中有两个环节是需要处理的:

● 具体产品和抽象产品之间的配置关系,例如:ProductA和ProductB,选择谁来生成IProduct?

● 具体工厂与抽象工厂之间的配置关系,也就是到底把哪个Concrete Factory返回给客户程序的问题。

●还有一个程序框架之外的因素,我们建好一个类库后往往还需要扩展,最好是无须修改现有框架就可以把新的程序集和新的类引入。

既然程序自己拆解不开耦合关系,那么就引入外部因素——.NET配置访问。我们看看上面那个量产的应用级抽象工厂的一个配置文件框架,其示例代码如下:

App.Config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <marvellousWorks.practicalPattern.abstractFacgtory.customFactories>
    <factories>
      <!--X系列产品的抽象工厂定义-->
      <add key="Category X Factory" value="***.ConcreteFactoryX, ***">
        <typeMapper type="***.ConcreteXTypeMapper, ***"/>
        <products>
          <add key="***.IProductXA, ***" value="***.ProductXA2, ***"/>
          <add key="***.IProductXB, ***" value="***.ProductXB1, ***"/>
        </products>
      </add>
      <!--Y系列产品的抽象工厂定义-->
      <add key="Category Y Factory" value="***.ConcreteFactoryY, ***">
        <typeMapper type="***.ConcreteYTypeMapper, ***"/>
        <products>
          <add key="***.IProductYA, ***" value="***.ProductYA1, ***"/>
          <add key="***.IProductYB, ***" value="***.ProductYB1, ***"/>
          <add key="***.IProductYC, ***" value="***.ProductYC1, ***"/>
        </products>
      </add>
    </factories>
  </marvellousWorks.practicalPattern.abstractFacgtory.customFactories>
</configuration>

您也可以把抽象工厂定义部分放在一个配置节,把TypeMapper放在另一个配置节,然后增加一个配置节,单独配置它们之间的映射关系,这样看起来更近似很多设计模式中非常提倡的方式——组合。不难发现,一个统一的ConfigurationManager可以为上面定义的类型AssemblyMechanism、TypeMapper 解决映射关系的定义问题,即便当以后的构造满足某个IProduct标准的Concrete Product时,需要使用该Concrete Product类型的带参数构造方法,一样可以在这个框架下继续扩展。例如可以在<products>的每个配置元素下增加一个配置元素集合,标示所有构造参数,其示例代码如下:

App.Config

<products>
  <add key="***.IProductYA, ***" value="***.ProductYA1, ***"/>
  <!--调用构造函数的初始化变量-->
  <parameters>
    <add name="message" type="System.String" value="hello world"/>
    <add name="unitPrice" type="System.Double" value="23.45"/>
  </parameters>
  <add key="***.IProductYB, ***" value="***.ProductYB1, ***"/>
  <add key="***.IProductYC, ***" value="***.ProductYC1, ***"/>
</products>

从这个示例配置框架可以看出,抽象工厂隐式依赖关系链导致的耦合性问题,其实可以通过更为通用的、固定的第三方渠道解决,关键是发现经典模式实现中的变化点。虽然《设计模式:可复用面向对象软件的基础》中每个模式都给出了类图表示的静态结构,但很多时候没有把客户程序画进去,即便提供了客户程序、抽象产品和具体产品的类图,要真正做到图中各个角色间松散耦合,往往还必须增加第三方协助解决。例如下面要介绍的Builder 模式,经典设计如图5-5所示。

图5-5 《设计模式:可复用面向对象软件的基础》中给出的创建者模式的静态结构

引自Design Patterns : Elements of Reusable Object-Oriented Software

图5-5中并没有Client,即便有Client,到底使用哪个Concrete Builder?谁传递给它?这些其实都需要借助第三方机制完成。除非Client自己 Builder builder=new ConcreteBuilder(),这等于一个直接的紧密耦合。随着各种框架类库的出现,控制反转和依赖注入渐渐成为开发中普遍的控制技术,而且基于配置文件的访问机制也成为各种工程化软件的趋势,本章的抽象工厂模式是一个很典型的使用情景,相对其他模式而言,它涉及一组“抽象类型/具体类型”的管理工作,而抽象工厂模式又非常适用于现实中较大规模的项目开发(比如面向半结构化开发的某个子系统关键对象的创建、领域驱动设计中面向某个业务领域相关对象的创建),因此是一个值得推荐的应用模式。一方面是希望使用,不过容易产生1∶N耦合,当您发现应用自己解决很有困难的时候,就把问题抛到外面去,推到配置部分是一个不错的选择,无论是运行维护还是在相对固定的框架下进行系统升级,都很方便,需要增加的工作量就以“八股”的方式使用.NET Framework的“4 件套”(ConfigurationElement、ConfigurationElementCollection、ConfigurationSection、ConfigurationSectionGroup)类型实现自己的配置对象。

5.5 基于Delegate的生产外包

在基本完成工厂方法模式和抽象工厂模式的介绍前,我们要额外留意一个问题,就是对象创建的时机。上面的介绍中都是客户程序访问某个抽象的工厂类型,获得抽象的产品,但本质上都是同步调用的,现实中这个过程往往不是这样的,比如您创建的是一个进程、一个立方体、一个地图对象,而客户程序往往就是个“Ready to work”,并没有真正“work”的对象,它对外提供的是一个异步响应;另一种情况您可能加工的是一个对象,但需要此对象的客户不只一个,无论是增加一个Observer还是Factory主动告诉各个“候着的”客户对象“您需要的对象已经加工好了”,最好有个获得加工完成消息的入口点。上面两种情况其实都有一个需求,即把同步的Factory变成可提供异步通知能力的Factory。

这和我们现实中生产情况汇报差不多,客户下单之后,不是客户盯着工厂生产了多少产品(当然,如果特别大宗的产品,或者只一个产品除外),然后数“一个、两个、三个……”,而是工厂完成任务后告诉客户“您来提货”吧。如果工厂还兼做二级代理的批发,那么它在加工完一批产品后还会通知多个代理商来提货。

对该过程进行抽象后的时序关系如图5-6所示。

图5-6 基于委托的异步抽象工厂

其中

1. 客户程序调用工厂的工厂方法,同时把加工后通知的委托也传递给工厂。

2. 工厂收到客户程序请求后,反馈 “知道了”,但并不返回加工产品成果。

3. 工厂继续完成产品的创建工作。

4. 待生产出IProduct之后,按照之前客户程序告诉它的委托,通知回去,完成整个的加工步骤。

对于没有委托或函数指针的语言,可以通过一个具有回调方法的接口完成,甚至通过我们在大型机时代就已经使用的很多方法完成,例如:命名管道、报文、共享文件,或者通过中间数据库完成。

异步工厂的一个实现结构,其示例代码如下:

C# 定义的抽象结构部分

public delegate void ObjectCreateHandler<T>(T newProduct);
public interface IFactory
{
    IProduct Create();
}
public interface IFactoryWithNotifier : IFactory
{
    void Create(ObjectCreateHandler<IProduct> callback);
}

Unit Test

[TestClass]
public class TestFactory
{
    class ConcreteProduct : IProduct{}
    class ConcreteFactory : IFactoryWithNotifier
    {
        /// 同步构造过程
        public IProduct Create() { return new ConcreteProduct(); }
        /// 异步构造过程
        public void Create(ObjectCreateHandler<IProduct> callback)
        {
          IProduct product=Create();
          callback(product);  // 异步地返回加工结果
        }
    }
    class Subscribe
    {
        private IProduct product;
        public void SetProduct(IProduct product) { this.product=product; }
        public IProduct GetProduct() { return this.product; }
    }
    [TestMethod]
    public void Test()
    {
        IFactoryWithNotifier factory=new ConcreteFactory();
        Subscribe subcriber=new Subscribe();
        ObjectCreateHandler<IProduct> callback =
          new ObjectCreateHandler<IProduct>(subcriber.SetProduct);
        Assert.IsNull(subcriber.GetProduct());
        factory.Create(callback);
        Assert.IsNotNull(subcriber.GetProduct());
    }
}

上述这个异步调用工厂的实现不仅适于简单工厂、工厂方法模式、抽象工厂模式,即便是纯粹的静态工厂,也一样适用,它的关键思想就是给构造过程增加第二个、第三个“预先安排好”的出口,参考第 1 章您会发现用委托同时表示多个出口非常容易,因为.NET Framework自带的事件机制就是这么实现的,在这个例子中,如果把简陋的委托换成事件,其实就和我们常用的事件响应没有区别,只不过需要扩展 EvertArgs,把加工好的对象作为事件参数的一个数性传递出去。

如果说控制反转做的是“Don't call me, I'll call you”,那么这里异步工厂做的就是“I'll call them, but a little later”。

5.6 小结

抽象工厂模式是简单工厂的升华,它除了解决对象构造过程后置之外,更主要的是提供了一组“相关或具有依赖关系”的加载过程,也就是具有了对一组类型构造过程组合和调度的能力;相对工厂方法而言,它更像一个“封装”的工厂,而不是简单地提供创建某个类型产品的“方法”。

使用中可以把工厂方法、静态工厂、简单工厂和抽象工厂混合起来,一个建议的布局是这样的:

● 静态工厂可以作为最底层的一个“泵”,它提供最原始但最粗颗粒度对象的创建,甚至生成object,后绑定调用(或被称为晚绑定)可以通过一个集中的静态工厂完成。之所以把它放在最下面,主要是因为它不能被继承和进一步扩展,所以让它做最基本但又最通用的工作。实际工程中完全用Activator充当这个角色。

● 简单工厂方法的适用范围很广,根据应用的需要在某些需要隔绝抽象与具体的位置上使用。

●工厂方法应用在具有一套较纵深层次的对象上,可以通过实体工厂类型选择继承层次上一个合适的具体产品来构造,而客户程序则把握住抽象的工厂类型和抽象的产品类型即可。

●抽象工厂方法则用在某些应用领域上,面向某些应用子系统或专业领域的对象创建工作,相对而言我们可以更多地把它应用于项目的中高层设计中。

以.NET应用的示范架构为例(见图5-7),静态工厂推荐用于Data Access Logic和Service Agent、Business Logic Layer的底部,简单工厂则可以用于各个层面,工厂方法可以用于每层具有一定层次关系的对象创建上,抽象工厂方法适合用于Service Agent和Business Entities等位置,用它产生一组“相关或相互依赖”对象的创建。

图5-7 典型.NET应用中的服务端布局(.NET Framework 2.0环境)