问题聚焦:
个人理解: 原型模式就是一个山寨工厂对某几个产品高仿几乎成真的批量生产过程。
意图:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
动机:Demo
实例:乐谱编辑器(图形编辑器框架+音符休止符五线谱)
功能:工具选择版,包括:将音乐对象加到乐谱中,选择、移动和其他操纵音乐对象的工具
相关类:
- Graphics类:框架为音符和五线谱这样的图形构建提供的抽象类
- Tool类:为选择板中的工具提供的抽象类
- GraphicTool类:为一些创建图形对象实例并将它们加入到文档中的工具预定义了一个子类
问题:音符和五线谱的类由我们的应用的设计而确定,而GraphicTool类属于框架,它不知道它即将创建的音乐实例的类型。
方案1:为每一种音乐对象创建一个GraphicTool的子类,但这样会产生大量的子类,这些子类仅仅在它们所初始化的音乐对象的类别上有所不同。
方案2:对象组合,参数化GraphicTool实例。
一个Graphic子类的实例来创建新的Graphic,我们称这个实例为一个原型。
GraphicTool将它应该克隆和添加到文档中的原型作为参数。
所有原型都支持一个clone操作,以让GraphicTool可以克隆所有种类的Graphic。
效果:用于创建音乐对象的每一种工具都是一个用不同原型进行初始化的GraphicTool实例。通过克隆每个音乐对象的原型并将这个克隆到乐谱中,每个GraphicTool实例都会产生一个音乐对象。
架构:
适用性:
- 当一个系统应该独立于它的产品的创建,构成和表示时
- 当要实例化的类是在运行时刻指定时,例如动态装载
- 为了避免创建一个与产品类层次平行的工厂类层次时
- 当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆它们可能币每次用合适的状态手工实例化该类更方便一些。
结构:
参与者:
- Prototype(Graphic):声明一个克隆自身的接口
- ConcretePrototype(Staff、WholeNote、HalfNote):实现一个克隆自身的操作
- Client(GraphicTool):让一个原型克隆自身从而创建一个新的对象
协作:客户请求一个原型克隆自身
效果:
- 对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目
- 使客户无需改变即可使用与特定应用相关的类
- 运行时刻增加和删除产品:注册原型实例就可以将一个新的具体产品类并入系统
- 改变值以指定新对象: 通过为一个对象变量指定新值,让其表现出新的行为,用该参数实例化已有类并将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。
- 改变结构以指定新对象:许多应用由部件和子部件构成,因此,应该允许重复使用一个特定的类。
- 减少子类的构造:Prototype模式使得克隆一个原型而不是请求一个工厂方法去产生一个新的对象
- 用类动态配置应用:一些运行时刻环境允许你动态将类装载到应用中。
- 一个希望创建动态载入类的实例的使用不能静态引用类的构造器,而应该由运行环境在载入时自动创建每个类的实例,并用原型管理器来注册这个实例。这样应用就可以向原型管理器请求新装载的类的实例,这些类原本没有和程序相连接。
缺陷:
每一个Prototype的子类都必须实现clone操作,这有时候很难实现。
实现:
当实现原型时,要考虑下面一些问题:
使用一个原型管理器:当一个系统中的原型数目不固定时,要保持一个可用原型的注册表,我们称这个注册表为原型管理器。原型管理器是一个管理存储器,返回一个与给定关键字相匹配的原型。
实现克隆操作:当对象结构包含循环引用时,克隆操作尤为棘手。
初始化克隆对象:克隆一个结构复杂的原型通常需要深拷贝,因为复制对象和援对象必须相互独立。因此你必须保证克隆对象的构件也是对原型的构件的克隆。
代码示例:
类设定:
- 定义MazeFactory的子类MazePrototypeFactory。该子类将使用它要创建的对象的原型来初始化,这样我们就不需要仅仅为了改变它所创建的墙壁或房间的类而生成子类了。
- MazePrototypeFactory用一个以原型为参数的构造器来扩充MazeFactory接口。
class MazePrototypeFactory : public MazeFactory
{
public:
MazePrototypeFactory(Maze*, Wall*, Room*, Door*);
virtual Maze* MakeMaze() const;
virtual Room* MakeRoom(int) const;
virtual Wall* MakeWall() const;
virtual Door* MakeDoor(Room*, Room*) const;
private:
Maze* _prototypeMaze;
Room* _prototypeRoom;
Wall* _prototypeWall;
Door* _prototypeDoor;
};
//新的构造器只初始化它的原型
MazePrototypeFactory::MazePrototypeFactory (
Maze* m, Wall* w, Room* r, Door* d
) {
_prototypeMaze = m;
_prototypeWall = w;
_prototypeRoom = r;
_prototypeDoor = d;
}
// 每个创建型成员函数都要克隆一个原型,然后初始化
Wall * MazePrototypeFactory::MakeWall() const
{
return _prototypeWall->Clone();
}
Door* MazePrototypeFactory::MakeDoor (Room* r1, Room* r2) const
{
Door * door = _prototypeDoor->Clone();
door->Initialize(r1, r2);
return door;
}
我们只需要使用基本迷宫构件的原型进行初始化,就可以由MazePrototypeFactory来创建一个原型的或缺省的迷宫
MazeGame game;
MazePrototypeFactory simpleMazeFactory (
new Maze, new Wall, new Room , new Door
);
Maze* maze = game.CreateMaze(simpleMazeFactory);
为了改变迷宫的类型,我们可以用一个不同的原型集合来初始化MazePrototypeFactory。
MazePrototypeFactory bombedMazeFactory(
new Maze, new BombedWall, new RoomWithABomb, new Door
);
一个可以被用作原型的对象,例如Wall的实例,必须支持Clone操作。它还必须有一个拷贝构造器用于克隆
如BombedWall子类必须重定义Clone并实现相应的拷贝构造器
class BombedWall : public Wall {
public:
BombedWall();
BombedWall(const BombedWall&);
virtual Wall* Clone() const;
bool HasBomb();
};
BombedWall::BombedWall (const BombedWall& other) : Wall(other)
{
_bomb = other._bomb;
}
Wall* BombedWall::Clone() const { // 虽然BombedWall::Clone返回一个Wall*, 但是它的实现返回了一个指向子类的新实例的指针,即BombedWall*.
return new BombedWall( *this );
}
相关模式
- Prototype和Abstract Factory模式在某种方面是相互竞争的。但是它们也可以一起使用,Abstract Factory可以存储一个被克隆的原型的集合,并且返回产品对象。
- 大量使用Composite和Decorator模式的设计通常也可以从Prototype模式处获益。