《设计模式》之Creational模式:Abstract Factory

3 篇文章 0 订阅
3 篇文章 0 订阅

对象创建:Abstract Factory



目的

提供一个可以创建一族关联的或者依赖的对象,而不需要指定它们具体的类。

有时也可称为

Kit

驱动

假设有一个用户接口工具箱,它可以支持多种不同的观感标准,例如Motif和Presentation Manager。不同的观感标准定义了不同的外观和用户界面行为,例如滚动条、窗口和按钮。要想在不同的观感标准之间方便的迁移,一个应用不应该写死自己的widget。在整个应用里到处实例化某一种观感标准的widget,导致后期很难修改为其它的观感标准。

我们可以使用一个抽象的WidgetFactory来解决这个问题,WidgetFactory为每种基本的widget声明了创建接口。同样,每一种widget也有一个相应的抽象类,具体的子类会针对不同的观感标准的widget具体的实现。WidgetFactory提供的接口可以真对每一个抽象的widget类创建widget对象。客户端可以调用这些接口来获得widget实例,但是客户端并不知道它们具体用了哪一个类。因此,客户端可以独立于观感标准。

这里写图片描述

对于每个观感标准,都有一个WidgetFactory与之对应。每一个子类实现了具体的操作来创建该观感标准的widget。例如,在MotifWidgetFactory中的CreateScrollBar操作可以实例化并且返回一个Motif的滚动条。客户端仅仅通过WidgetFactory接口来创建widget,而无需了解到底是哪种观感标准类实现了这个widget。也就是,客户端直接交互的是抽象类的接口,而不是某一个具体的类。

WidgetFactory同样加强了某一标准下,不同widget的依赖关系。例如,一个Motif滚动条应该和Motif按钮和Motif文本编辑器同时使用,这种约束关系自然而然的被加强了,因为它们都通过MotifWidgetFactory来实现。

适用

可以在一下情况下适用Abstract Factory模式:

  1. 系统需要独立于它的“产品(product)”如何被创建、组成和表示时。
  2. 系统面对不同的产品家族,只需要配置其中一个时。
  3. 一个家族的关联产品对象需要被一起配合使用时,你需要强化这种限制时。
  4. 你希望提供一个产品类库,你只想暴露它们的接口,而不是它们的实现时。

结构

这里写图片描述

成员

  • AbstractFactory(WidgetFactory):对创建抽象产品对象的接口的声明。
  • ConcreteFactory(MotifWidgetFactory,PMWidgetFactory):实现创建具体产品对象的操作。
  • AbstractProduct(Window,ScrollBar):给某种产品对象声明一个接口。
  • ConcreteProduct(MotifWindow,MotifScrollBar):定义一个产品对象,这个对象可以被其相应的ConcreteFactory创建;实现AbstractProduct接口。
  • Client:只使用AbstractFactory和AbstractProduct提供的接口。

合作

  • 正常情况下只有一个ConcreteFactory实例在运行时被创建。这个具体的工厂可以创建特定的产品。客户端要想创建不同的产品,需要使用不同的具体工厂。
  • AbstractFactory将创建产品对象的过程交给ConcreteFactory子类。

结果

Abstract Factory模式有以下的优点和责任:

  1. 它隔离了具体的类。Abstract Factory模式帮助你控制应用创建的对象。因为一个工厂封装了创建一个产品对象的责任和过程,它将客户端和类的具体实现隔离开来。客户端通过抽象接口来操作实例。产品的类的具体名字被隔离在具体的工厂里,它们不会出现在客户端中。
  2. 它使得产品家族的切换变得容易。在应用中,具体工厂的类只出现一次,也就是在它被实例化的时候。这就使得在应用中改变具体工厂类变得很容易。应用可以修改具体的工厂,来使用不同的产品配置。因为一个抽象工厂会包含某一个家族的所有产品,所以整个产品家族可以通过这个抽象工厂一次性修改。在我们的例子中,我们能够从Motif widget转换成Presentation Manager widget,只需要将抽象工厂修改成对应的具体的工厂。
  3. 提高了产品的一致性。当一个家族的产品需要一起使用时,对应用而言,应该每次只使用某一个家族的产品。AbstractFactory可以用来加强这种关系。
  4. 不易支持新的产品。扩展已有的抽象工厂来使其能够创建新品种的产品是不太容易的。因为在AbstractFactory里,提供的接口已经固定了能够创建的产品类型。支持新类型的产品需要扩展工厂的接口,这包括AbstractFactory类和其子类的接口。我们会在下面具体实现时,提供一个解决这个问题的方法。

实现

这里介绍一下实现Abstract Factory模式的一些技术。

单例模式

一个应用对某个产品家族而言,只需要一个ConcreteFactory实例即可。所以,最好情况下实现为一个Singleton。

创建product

AbstractFactory只声明创建产品的接口,具体的创建过程交给对应的ConcreateProduct子类来实现。最常用的方法是对每个产品使用工厂方法Factory Method。一个具体的工厂将会重写每个工厂方法来生产具体的产品。这种实现方法很简单,每个产品家族都需要有自己的具体的工厂子类,尽管这些产品家族之间不尽相同。

如果有许多产品家族,concrete factory可以利用Prototype模式来实现。Concrete factory可以利用每个产品的原型实例来初始化。基于Prototype的方法消除了对每个新的产品家族都需要实现新的concrete factory的麻烦。

这里有一种使用Smalltalk来实现基于Prototype的factory的方法。Concrete factory 将prototype存储在一个叫partCatalog的字典里。方法make会获取这个原型并且进行克隆:

make: partName
    (partCatalog at: partName) copy

Concrete factory有一个向catalog中添加part的方法:

addPart: partTemplate named: partName
    partCatalog at: partName put: partTemplate

可以用一个标识符将Prototype加入factory:

aFactory addPart: aPrototype named: #ACMEWidget

这种基于Prototype的改进可以在一些特殊的编程语言中实现,这些语言将类看做基本的对象,例如Smalltalk、Objective C。在这个例子中,你可以将类看做一种退化的factory,这个factory它只能构建一种product。你可以将product类像变量一样存储在concrete factory中,因此factory可以创建各种各样的product,就像prototype一样。

定义可扩展元素

AbstractFactory一般会对每个种类的product定义不同的函数,product的种类一般会在函数名称上显示出来。添加新的product需要修改Abstract提供的接口和所有相关的类。

一个更为灵活但是不太安全的做法是给函数添加参数,这些参数决定了这些函数将要构建哪个product。这个参数可以是类标识符、整数、数组或者其它可以指明这个product的标记。这样,AbstractFactory 只需要一个函数“Make”,它的参数指明了需要构建的对象。这种方法已经在先前讨论的基于Prototype的factory中讨论过。

这个方法在动态类型的语言中更易使用,例如Smalltalk。在静态语言中如C++,则需要所有的product都有公共的抽象基本类,或者这些product可以被client安全地强制转化成正确的类型。在Factory Method章节中,会讲述如何用C++实现参数化的操作。

即使不需要强制类型转化,因为继承导致的问题依然存在:所有返回给client的product都有着相同的抽象类型和相同的抽象接口。Client将无法区别和安全的推断这些product的类。如果client需要调用子类的函数,则在这个抽象类中无法使用。尽管可以使用向下类型转换,例如在C++中的dynamic_cast,但是这不总是可行的,因为向下投影可能会失败。这是在设计高灵活性和扩展性接口的是需要考虑的权衡问题。

样例代码

我们将使用Abstract Factory模式来创建一个maze,maze游戏已经在上一章节中讨论过。

类MazeFactory可以创建maze的组件。它可以创建room、wall和door。其它程序可以读取文件中的设计计划,使用MazeFactory来创建相应的maze。其它程序也可以利用MazeFactory来随机的创建一个maze。创建maze的程序会将MazeFactory作为一个变量,从而程序员可以指定room、wall和door的种类。

calss MazeFactory{
public:
    MazeFactory();

    virtual Maze* MakeMaze() const
        {return new Maze;}
    virtual Wall* MakeWall() const
        {return new Wall;}
    virtual Room* MakeRoom(int n) const
        {return new Room(n);}
    virtual Door* MakeDoor(Room* r1, Room* r2) const
        {return new Door(r1, r2);}
};

上一章节中使用的函数CreateMaze函数,创建了一个小maze,有两个房间,和一个连接这两个房间的门。CreateMaze写死了类的名字,使得其难以转变成其它组件构成的maze。

这里有一个利用MazeFactory来消除这个缺点的CreateMaze,它将MazeFactory作为一个参数:

Maze* MazeGame::CreateMaze (MazeFactory& factory){
    Maze* aMaze=factory.MakeMaze();
    Room* r1=factory.MakeRoom(1);
    Room* r2=factory.MakeRoom(2);
    Door* aDoor=factory.MakeDoor(r1,r2);

    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);

    r1->SetSide(North, factory.MakeWall());
    r1->SetSide(East, aDoor);
    r1->SetSide(South, factory.MakeWall());
    r1->SetSide(West, factory.MakeWall());
    r2->SetSide(North, factory.MakeWall());
    r2->SetSide(East, factory.MakeWall());
    r2->SetSide(South, factory.MakeWall);
    r2->SetSide(West, aDoor);
    return aMaze;
}

我们可以创建EnchantedMazeFactory,一个魔法迷宫的factory,是MazeFactory的子类。EnchantedMazeFactory将会重写不同的成员函数,同时返回不同的Room、Wall的子类。

class EnchantedMazeFactory: public MazeFactory{
public:
    EnchantedMazeFactory();

    virtual Room* MakeRoom(int n) const
    {return new EnchantedRoom(n,CastSpell());}

    virtual Door* MakeDoor(Room* r1, Room* r2) const
    {return new DoorNeedingSpell(r1,r2);}

protected:
    Spell* CastSpell() const;
}

假如我们想要一个可以在room放置炸弹的迷宫游戏,如果这个炸弹爆炸了,它将会损耗墙壁。我们可以新建一个Room的子类来追踪这个room里是否有个炸弹和炸弹是否已经爆炸。我们也需要一个Wall的子类来追踪墙所受伤害。我们将这两个类称作RoomWithABomb和BombedWall。

我们最后定义的是一个BombedMazeFactory,一个MazeFactory的子类,它可以确保wall都是BombedWall,room为RoomWithABomb。BombedMazeFactory只需要重写两个函数:

Wall* BombedMazeFactory::MakeWall()const{
    return new BombedWall;
}

Room* BombedMazeFactory::MakeRoom(int n)const{
    return new RoomWithABomb(n);
}

要建立一个有炸弹的迷宫,我们可以简单的使用CreateMaze,将BombedMazeFactory作为参数传递给它。

MazeGame game;
BombedMazeFactory factory;
game.CreateMaze(factory);

CreateMaze也可以使用EnchantedMazeFactory来创建一个魔法迷宫。

注意到MazeFactory是一个factory方法集合,这是比较常见的实现Abstract Factory 模式的方式。这里,MazeFactory不是一个抽象类;因此它即作为AbstractFactory又作为ConcreteFactory。这又是一个比较常见的实现Abstract Factory的方法。因为MazeFactory是一个具体类,包含了所有的factory方法。创建一个新的Factory只需要设置成MazeFactory的子类,重写一些必要的方法。

CreateMaze利用SetSide函数来指定room的边。如果RoomWithABomb需要访问BombedWall子类特有的成员,则需要将Wall向下投影到BombedWall。这种投影是安全的,Room中的Wall确实就是BombedWall,因为他们都是通过BombedMazeFactory创建的。

已知的使用

Interviews使用“Kit”后缀来表示AbstractFactory类,它定义了WidgetKit和DialogKit抽象factory来生成不同观感的UI组件。Interview也包含了一个LayoutKit,可以根据需求生成不同的组合对象。例如,一个水平layout可能根据文本方向,需要不同的组成对象。

ET++使用AbstractFactory模式来实现跨平台(如,X Windows 和SunView)的可移植性。WindowSystem抽象基础类定义了创建窗口组件的接口(如MakeWindow,MakeFont,MakeColor)。具体的子类根据不同的窗口系统实现相应的接口。在运行时,ET++创建一个WindowSystem子类,它会负责创建系统组件对象。

相关模式

AbstractFactory 类 通常使用Factory Method来实现,但是也可以使用Prototype来实现。一个具体的factory经常是一个Singleton。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值