《设计模式》—创建型模式—原型模式

一、问题来源

考虑一个应用于绘制五线谱的工具。我们需要一个抽象的Graphic类,其子类包括不同长度的音符类。同时我们需要提供一个工具类GraphicTool用于创建图形实例。然而,与前面我们讨论过的工厂方法模式一样。对于框架来说,它并不知道自己要实例化的对象是属于Graphic的哪个子类的。
那么我们是否可以考虑使用工厂方法模式来解决此问题呢?可以,但是这样的结果就是为了创建一个具体的Graphic类,我们需要一个具体的Creator类,非常冗余。这里就可以考虑使用我们这里所说的原型模式。
原型模式的想法是为每个具体的产品类创建一个GraphicTool对象。这个类的构造函数中需要传入一个产品类对象,即原型。因此我们在引入新产品类的时候,我们不需要再增加对应的Creator类。只需以新增的产品类对象为原型构建一个新的原型实例。
在我们这个例子中,进一步应用原型模式,我们可以再次减少类的数量。不同音长的音符可以由不同的位图和时延原型进行初始化。那么音符类可以简化为由不同位图实例衍生出的客户实例。这种想法和组合模式不谋而合。

二、类图

在这里插入图片描述
这里,原型类和构建类之间一般是聚合的关系。构建类使用抽象原型类的对象为成员变量,创建基于该类的新对象。

三、适用范围

1、当一个系统应该独立于它的产品创建、构成和表示时

这句话再熟悉不过,和抽象工厂模式如出一辙。那么运行模式是如何将产品的维护独立于系统之外呢?具体问题可能有具体的解决方案。以抽象工厂模式中我们维护不同系统的gui工具箱为例,我们通过读取系统信息可以拿到当前系统的类型。原型模式支持在程序中动态注册和删除原型。我们只需以相同的字符串为key,在windows中仅仅注册windows相关的界面类;在linux中只注册linux相关的界面类。这样对用户来说,只需要了解keywidget的对应关系,而不需知道在不同系统中拿到的是哪个原型的克隆。

2、当要实例化的类是在运行时指定时,例如,通过动态装载

如果类的加载是动态的,例如在C++中实现加载一个动态库,而所加载的动态库是由客户所指定的。那么在我们在代码中意图构建一个对象时,我们甚至可能不知道它具体的类。这种情况下,我们可以通过注册原型和获取原型创建对象的方式避免过多的分支并实现代码复用。

3、为了避免创建一个与产品类层次平行的工厂类层次时

我们讨论抽象工厂模式时提到过,对于一类产品,同一类产品的系列数会决定工厂的数量。因此我们使用原型模式可以大大减少我们需要新增的类个数。

4、当一个类的实例只能有几个不同状态组合中的一种时

建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

四、参与者

(1)Prototype

声明一个克隆自身的接口,可为抽象类。

(2)ConcretePrototype

具体产品类,实现克隆自身的接口

(3)Client

保存ConcretePrototype对象。通过克隆该对象可以得到该产品的新对象。

五、优点

1、向客户隐藏了具体的产品类

正如我们前面讨论过的,客户并不需要关注具体的产品是什么。产品的注册和创建全部放在具体的Client对象和产品类中完成。

2、运行时增加和删除产品

关于产品注册,我们可以看下JDBC中不同数据库驱动的注册,通过Class.forName接口实现。虽然其产品的创建没有使用原型模式,但是借助这种注册的方式也可以让我们更方便的使用原型模式。

3、改变值以指定新对象

前面讨论工厂方法模式的时候,我们提到过参数化工厂方法。如果把不同参数的工厂方法所创建出的对象指定给原型模式,那么我们就可以快速扩充原型的数量。客户通过将不同的行为代理给不同参数所创建的对象就可以表现出不同的行为。

4、改变结构以指定新对象

我们可以通过对象组合的方式迅速创建新对象。像我们设计电路的软件中会提供元器件工具箱。同时,工具箱也支持自定义,即我们通过组合不同的元器件形成的子电路可以再次添加到工具箱中。这里的工具箱我们就可以认为是一个原型的集合。同时,这样的子对象应该支持递归的进行组合。唯一需要注意的就是拷贝时需要实现深拷贝。

5、减少子类的构造

这里的产品类不需要对应的Creator类,而只需要不同的Client类实例,因此大大减少了类的数量。这一点对C++语言更为有用。因为在像JAVA这样的语言中,类的元对象已经起到和原型一样的作用了。

六、缺点

所有产品都需要实现一个拷贝自身的接口。对于已存在的类它们内部的拷贝操作我们已无法修改,且无法为它们新增clone接口。

七、实现

1、原型管理器

类动态注册和删除需要通过原型管理器完成。客户在创建对象前需要向原型管理器请求原型。原型管理器一般通过关键字进行操作。

2、浅拷贝or深拷贝

浅拷贝通常是足够的,只有当我们需要改变对象时才需要复制一个副本并修改相应的值。

3、初始化克隆对象

即使所有的产品类都实现了Clone接口,也有些产品对象确实需要一些特殊的初始化接口。那么这时也许我们就需要使用RTTI去实现特定操作。

八、代码示例

// CLS_Graphic.h
#pragma once

struct POINT
{
	int x;
	int y;
};

class CLS_Graphic
{
public:
	virtual void draw(POINT p) = 0;
	virtual CLS_Graphic* clone() = 0;
	virtual ~CLS_Graphic() {};
};
// CLS_MusicalNote.h
#pragma once
#include "CLS_Graphic.h"
class CLS_MusicalNote : public CLS_Graphic
{
protected:
	int m_iType;
public:
	virtual void draw(POINT p) = 0;

	virtual CLS_MusicalNote* clone() = 0;
};
// CLS_HalfNote.h
#pragma once
#include "CLS_MusicalNote.h"
class CLS_HalfNote : public CLS_MusicalNote
{
public:
	virtual void draw(POINT p);

	virtual CLS_HalfNote* clone();
};
// CLS_HalfNote.cpp
#include "CLS_HalfNote.h"

void CLS_HalfNote::draw(POINT p)
{
}

CLS_HalfNote* CLS_HalfNote::clone()
{
	return new CLS_HalfNote();
}
// CLS_WholeNote.h
#pragma once
#include "CLS_MusicalNote.h"
class CLS_WholeNote : public CLS_MusicalNote
{
public:
	virtual void draw(POINT p);

	virtual CLS_WholeNote* clone();
};
// CLS_WholeNote.cpp
#include "CLS_WholeNote.h"

void CLS_WholeNote::draw(POINT p)
{
}

CLS_WholeNote* CLS_WholeNote::clone()
{
	return new CLS_WholeNote();
}
// CLS_PrototypeManager.h
#pragma once
#include <string>
#include <map>
class CLS_PrototypeManager
{
private:
	CLS_PrototypeManager() {};

	static std::map<std::string, void*> m_mapPrototype;
public:
	static void registerPrototype(std::string key, void* pointer);

	static void unregisterPrototype(std::string key);

	static void* getPrototype(std::string key);
};
// CLS_PrototypeManager.cpp
#include "CLS_PrototypeManager.h"

std::map<std::string, void*> CLS_PrototypeManager::m_mapPrototype;

void CLS_PrototypeManager::registerPrototype(std::string key, void* pointer)
{
	if (pointer != nullptr)
	{
		m_mapPrototype.insert(make_pair(key, pointer));
	}
}

void CLS_PrototypeManager::unregisterPrototype(std::string key)
{
	m_mapPrototype.erase(key);
}

void* CLS_PrototypeManager::getPrototype(std::string key)
{
	auto iter = m_mapPrototype.find(key);
	if (iter != m_mapPrototype.end())
	{
		return iter->second;
	}

	return nullptr;
}
// CLS_GraphicTool.h
#pragma once
#include "CLS_Graphic.h"
#include <string>

const std::string CONST_HALFNOTE = "halfNote";
const std::string CONST_WHOLENOTE = "wholeNote";

class CLS_GraphicTool
{
private:
	CLS_Graphic* m_pGraphicProto;
public:
	CLS_GraphicTool(CLS_Graphic*);

	static void init();

	static void unInit();
};
// CLS_GraphicTool.cpp
#include "CLS_GraphicTool.h"
#include "CLS_PrototypeManager.h"
#include "CLS_HalfNote.h"
#include "CLS_WholeNote.h"

CLS_GraphicTool::CLS_GraphicTool(CLS_Graphic* _pGraphic)
{
	m_pGraphicProto = _pGraphic;
}

void CLS_GraphicTool::init()
{
	static CLS_HalfNote staticHalfNote;
	CLS_GraphicTool* pProtoHalfNote = new CLS_GraphicTool(&staticHalfNote);
	CLS_PrototypeManager::registerPrototype(CONST_HALFNOTE, pProtoHalfNote);

	static CLS_WholeNote staticWholeNote;
	CLS_GraphicTool* pProtoWholeNote = new CLS_GraphicTool(&staticWholeNote);
	CLS_PrototypeManager::registerPrototype(CONST_WHOLENOTE, pProtoWholeNote);
}

void CLS_GraphicTool::unInit()
{
	CLS_GraphicTool* pProtoHalfNote = static_cast<CLS_GraphicTool*>(CLS_PrototypeManager::getPrototype(CONST_HALFNOTE));
	CLS_PrototypeManager::unregisterPrototype(CONST_HALFNOTE);
	delete pProtoHalfNote;

	CLS_GraphicTool* pProtoWholeNote = static_cast<CLS_GraphicTool*>(CLS_PrototypeManager::getPrototype(CONST_WHOLENOTE));
	CLS_PrototypeManager::unregisterPrototype(CONST_WHOLENOTE);
	delete pProtoWholeNote;
}
// test.cpp
#include "Prototype/CLS_PrototypeManager.h"
#include "Prototype/CLS_Graphic.h"
#include "Prototype/CLS_GraphicTool.h"

int main()
{
	CLS_GraphicTool::init();
	
	CLS_Graphic* pHalfNote = static_cast<CLS_Graphic*>(CLS_PrototypeManager::getPrototype(CONST_HALFNOTE));
	CLS_Graphic* pWholeNote = static_cast<CLS_Graphic*>(CLS_PrototypeManager::getPrototype(CONST_WHOLENOTE));

	CLS_Graphic* pHalfNoteCopy = pHalfNote->clone();
	CLS_Graphic* pWholeNoteCopy = pWholeNote->clone();

	CLS_GraphicTool::unInit();
}

九、相似设计模式比较

我们可以使用原型模式实现抽象工厂模式,需要注意的就是确定好原型管理类和原型注册的时机。正如上面的代码所示,我们可以选择在具体工厂中提供初始化方法将所有需要的原型都注册。不过每次增加产品我们的初始化接口都需要做相应的修改。在原型管理类的选择上,我们可以考虑直接以抽象工厂作为原型管理类。向上面的代码一样,只需要提供静态方法进行原型的注册和释放,将之作为一个工具类使用即可。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值