设计模式读书笔记(二)--创建型模式
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而想显示它们的接口而不是实现时。
(3)结构图:
(4)参与者:
- AbstractFactory:声明一个创建抽象产品对象的操作接口。
- ConcreteFactory:实现具体产品对象的操作。
- AbstractProduct:为一类产品对象声明一个接口。
- ConcreteProduct:定义一个将被相应的具体工厂创建的产品对象;实现AbstractProduct接口。
- Client:仅使用由AbstractFactory和AbstractProduct类声明的接口。
(5)优缺点:
- 分离了具体的类:AbstractFactory模式帮助你控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。
- 它使得易于交换产品系列:一个具体工厂类在一个应用中仅出现一次--即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。
- 它有利于产品的一致性:当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要。而AbstractFactory很容易实现这一点。
- 难以支持新种类的产品:难以扩展抽象工厂以生产新种类的产品。这是因为AbstractFactory接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口,这将涉及AbstractFactory类及其所有子类的改变。
(6)实现抽象工厂模式的一些有用的技术:
- 将工厂作为单件:一个应用中一般每个产品系列只需一个ConcreteFactory的实例。因此工厂最好实现为一个Singleton。
- 创建产品:AbstractFactory仅声明一个创建产品的接口,真正创建产品是由ConcreteFactory子类实现的。最通常的一个办法是为每一个产品定义一个工厂方法。一个具体的工厂将为每个产品重定义该工厂方法以指定产品。虽然这样的实现很简单,但它却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。
- AbstractFactory通常用工厂方法实现,但它们也可以用Prototype模式实现。
- 一个具体的工厂通常是一个单件。
(7)代码实现:
class MapSiteBase { public: MapSiteBase(){ cout<<"mapsite base."<<endl; } virtual ~MapSiteBase(){}//虚析构函数,保险 virtual void Enter() = 0;//为子类定义统一接口,派生类需要实现它 };
接下来是各个组成的定义,类当中的一些成员函数没有特别的去实现,大多数默认为空少数提供打印显示,主要是为了展示设计模式:
class Room : public MapSiteBase { public: Room(int roomNo){} ~Room(){} virtual void Enter(){} private: int _roomNo; }; class Door : public MapSiteBase { public: Door(Room* = 0,Room* = 0){}//一面墙紧挨着两个房间 virtual void Enter(){} private: Room* _romm1; Room* _romm2; }; class Wall : public MapSiteBase { public: Wall(){} virtual void Enter(){} };
以上是三个组件的简单类定义,组件构成迷宫,简单说wall & door 构成 room,一些列room构成迷宫,需要定义一个迷宫类:
class Maze { public: Maze(){} void addRoom(Room*){}//往迷宫中添加Room Room* RoomNo(int) const{} };
迷宫的基本组件都定义完毕,接下里就是构建过程,定义一个迷宫构建类,简单说是时候写一个迷宫游戏了:
class MazeGame { public: MazeGame(){} virtual ~MazeGame(){} virtual Maze* createMaze(MazeFactory &factory);//创建迷宫的方法 virtual Maze* createMaze(MazeBuilder &builder);//可忽略,builder中用到 };
由上面可以看到这个时候去实现Maze中的createMaze这个成员函数就可以构造迷宫了,仔细想想,最简单暴力的就是直接在这个成员函数中new Room、Door、Wall,然后一一组合制定之间的关系,构成一个复杂的迷宫。但是万一需要改变一下迷宫的规则或者添加一些特性比如施有魔法的迷宫等,就要重新对这个成员函数进行编码了。疑问先晾在这,后面自会明朗。
为了让createMaze变得更加灵活,减少编码,定义一个可扩张的创建迷宫游戏的类:
class MazeFactory { public: MazeFactory(){} virtual ~MazeFactory(){} virtual Maze* makeMaze() const//成员函数都默认实现创建普通的迷宫,定义为virtual为了派生类实现他们感兴趣的方法 { return new Maze();//立即返回 } virtual Wall* makeWall() const { return new Wall(); } virtual Room* makeRoom(int num) const { return new Room(num); } virtual Door* makeDoor(Room* r1,Room* r2) const { return new Door(r1,r2); }
}
接下来看一下createMaze的简单实现,传入的参数是上述类的对象,通过这个参数调用对应的成员函数,实现创建迷宫,咋一看实现比之前明朗一点。创建一个简单的两个Room,一个Door的迷宫。
Maze* MazeGame::createMaze(MazeFactory &factory) { Maze *maze = factory.makeMaze(); Room *r1 = factory.makeRoom(1); Room *r2 = factory.makeRoom(2); Door *door = factory.makeDoor(r1,r2); maze->addRoom(r1); maze->addRoom(r2); /*wall init*/ }
上面创建了这么多类,也许有点混乱了,按道理来说作者应该给出一张UML图来表明一下这些类的关系,但我不能给出,希望有心人自己可以把它们画出来,往往是提供了UML有些读者才不会认真的去构思其中的联系。现在开始讲述抽象工厂这个设计模式了,我们先看一下上述一大堆的定义,该怎么用了,简单TEST:
Maze *maze;//声明一个迷宫 MazeFactory fac ;//声明一个创建迷宫游戏的工厂!! MazeGame mgame;//声明一个迷宫游戏 maze = mgame.createMaze(fac);//构建迷宫游戏,得到迷宫的handle赋值给maze。
这里有读过其他博客的读者或许会怀疑,抽象工厂模式应该是有一个抽象工厂定义接口,一个实际工厂实现这些接口。是的,然而MazeFactory 充当了两个的角色,因为MazeFactory 有缺省的实现,所以既可以是抽象工厂也可以是实际工厂。下面继续说明一个例子,也许会让读者对这个模式的优点有更深的认识。
是的,如果要创建一个带有魔法的Room了,需要spell的Door呢,是否又要重新编码了?来看一下抽象工厂模式是怎么实现的吧,下面是一系列类的定义:
class Spell//咒语类,给迷宫的一些组件增加点color { public: Spell(){} void show(){cout<<"this room is magic!"<<endl;} }; //定义一个魔法Room,继承父类Room class EnchantedRoom : public Room { public: EnchantedRoom(int num,Spell *spell):Room(num){spell->show();} //构造函数的参数包括Room的number,还有带spell的属性,:Room(num)这个加入主要是为了基类的构造函数参数个数类型不一致,派生类的参数与基类构造函数的参数不一致时,需要在派生类中指定基类的参数。 }; //定义一个Door,需要spell才能open的Door class DoorNeedingSpell : public Door { public: DoorNeedingSpell(Room* = 0,Room* = 0){show();} void show(){cout<<"this door need spell!"<<endl;} }; //you should be careful!定义一个工厂继承MazeFactory,具体重载了makeRoom和makeDoor这两个创建迷宫组件的方法。 class EnchantedMazeFactory : public MazeFactory { public: EnchantedMazeFactory(){} Spell* CastSpell() const{return new Spell();} virtual Room* makeRoom(int num) const { return new EnchantedRoom(num,CastSpell()); } virtual Door* makeDoor(Room* r1,Room* r2) const { return new DoorNeedingSpell(r1,r2); } };
当然如果感兴趣还可以加入一个会爆炸的Wall进去,书中有稍微说明了以下,这边就不花篇幅描述了,原理差不多。意在阐释这下我要是修改一些迷宫的特性不需要去修改createMaze这个方法也不需要去动基类Room、Wall的定义。一切靠派生出来的类即能解决,下面看一下上面例子的TEST:
Maze *maze; MazeFactory *fac = new EnchantedMazeFactory(); MazeGame mgame; maze = mgame.createMaze(*fac);
4、BUILDER(生成器)--对象创建型模式
(1)意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
(2)适用性:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不通的表示时。
(3)结构:
(4)参与者:
- Builder:为创建一个product对象的各个部件指定抽象接口;
- ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件;定义并明确它的各个表示;提供一个检索产品的接口;
- Director:构造一个使用Builder接口的对象;
- Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
(5)效果:
- 它使你可以改变一个产品的内部表示;
- 它将构造代码和表示代码分开;
- 它使你可对构造过程进行更精细的控制。
(6)代码:
上面注意到createMaze方法有两个,当中有一个传入的参数是MazeBuilder对象,从地位上来讲你也许会联想到,这个类是一个跟抽象工厂基类同等级别的存在:
virtual Maze* createMaze(MazeBuilder &builder);
接下来先定义一个MazeBuilder 类,看类名就知道这是该设计模式的core了:
class MazeBuilder { public: virtual ~MazeBuilder(){} //跟抽象工厂模式类似,定义一系列创建迷宫的接口,缺省实现为空 virtual void buildMaze(){} virtual void buildRoom(int num){} virtual void buildDoor(int fromR,int toR){} virtual void buildWall(){} virtual Maze* getMaze(){return 0;}//get 方法获得迷宫的handle,需要构建到最后一步才能返回 protected: MazeBuilder(){} };
当然这里也可将MazeBuilder实现成同MazeFactory一样有缺省实现的类,也就是每个build方法中有对应的简单实现,这里就不多赘述了。下面看一下迷宫游戏是如何被创建的:
Maze* MazeGame::createMaze(MazeBuilder &builder) { builder.buildMaze(); builder.buildRoom(1); builder.buildRoom(2); builder.buildDoor(1,2); return builder.getMaze(); }
In builder pattern,MazeGame class is like a Director class,Director class 是一个使用Builder接口的类。像其他创建型模式,builder设计模式封装了对象是如何创建的过程,通过MazeBuilder这个类所定义的接口进一步封装。这样一类我们可以重载MazeBuilder中的方法实现不同种类的迷宫,或者重载createMaze来实现不同结构的迷宫。比如创建一个标准迷宫:
class StandarMazeBuilder : public MazeBuilder { public: StandarMazeBuilder(){_currentMaze = 0;} //只重载自己感兴趣的方法 void buildMaze(){_currentMaze = new Maze();} void buildRoom(int num){ Room *r1 = new Room(num); _currentMaze->addRoom(r1); } void buildDoor(int fromR,int toR){ Room *r1 = _currentMaze->RoomNo(fromR); Room *r2 = _currentMaze->RoomNo(toR); Door *d = new Door(r1,r2); DoorSpell(); } //virtual void buildWall(){} Maze* getMaze(){return _currentMaze;}//灵魂实现,通过此接口向客户提供迷宫产品的handle private: Maze *_currentMaze; void DoorSpell(){cout<<"this door needs spell!"<<endl;}
};
MazeBuiler类自己并不创建迷宫,他只是提供一系列公共接口,它的派生类才做实际的工作,下面是Builder的TEST:
Maze *maze; MazeBuilder *mz = new StandarMazeBuilder(); MazeGame mgame; maze = mgame.createMaze(*mz); maze = mz->getMaze();
至此,会发现该模式与抽象工厂模式真像,都可以创建复杂的对象,主要区别是Builder模式注重一步一步构造一个复杂的模式,二抽象工厂模式着重于多个系列的产品对象。Builder在最后一步返回产品,而Factory是立即返回的。
5、Abstract Factory模式和Builder模式的区别:
两种模式的共同点使得在刚刚开始学习的时候,非常容易混淆,其实仔细研究,两者之间的区别也是非常明显的,而我认为两者之间最本质的区别是,抽象工厂通过不同的构建过程生成不同的对象表示,而Builder模式通过相同的构建过程生成不同的表示。builder 也是一个高层建筑,但是他和Abstract Factory侧重点不同,Abstract Factory侧重于创建东西的结果,而builder侧重的是创建东西的过程。当你需要做一系列有序的工作来完成创建一个对象时 builder就派上用场啦。