设计模式之原型(prototype)模式
相信大多数的人都看过《西游记》,对孙悟空拔毛变出小猴子的故事情节应该都很熟悉。孙悟空可以用猴毛根据自己的形象复制出很多跟自己一模一样的小猴兵出来,其实在设计模式中也有一个类似的模式,我们可以通过一个原型对象来克隆出多个一模一样的对象,这个模式就是原型模式。
一 大同小异的工作周报
M公司一直在使用自行开发的一个OA系统进行日常工作办理,但在使用过程中,越来越多的人对工作周报的创建和编写模块产生了抱怨。追其原因,M公司的OA管理员发现,由于某些岗位每周工作存在重复性,工作周报内容都大同小异,如下图所示:
这些周报只有一些小地方存在差异,但是现行系统每周默认创建的周报都是空白报表,因此用户只能通过重新输入或不断地复制与粘贴来填写重复的周报内容,极大地降低了工作效率,浪费宝贵的时间。如何快速创建相同或者相似的工作周报,成为了M公司软件开发人员的一个新问题。
M公司开发人员经过分析,决定按照以下思路对工作周报模块进行重新设计:
(1)除了允许用户创建新周报外,还允许用户将创建好的周报保存为模板(也就是原型)。
(2)用户在再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一个相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报。
二 原型模式概述
2.1 关于原型模式
原型模式的原理很简单,将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象克隆自己来实现创建过程。
原型模式(Prototype):使用原型实例指定创建对象的种类,并且通过拷贝这些原 型创建新的对象。原型模式是一种对象创建型模式。
需要注意的是,通过克隆方法所创建的对象时全新的对象。
2.2 类图
2.3 代码实现
2.3.1 抽象克隆类
class AbstractClone { protected: AbstractClone() { //cout << "AbstractClone Construct" << endl; } public: ~AbstractClone() { //cout << "AbstractClone Deconstruct" << endl; } virtual AbstractClone* Clone() = 0; virtual void PrintWeekly() = 0; };
2.3.2 周报类
class CWeeklyClone : public AbstractClone { public: /*CWeeklyClone() { cout << "CWeeklyLogClone Construct" << endl; }*/ CWeeklyClone(string strName="", string strDate="", string strContent="", CAttachment *pAttachment=NULL) { //cout << "CWeeklyLogClone Construct" << endl; m_strName = strName; m_strDate = strDate; m_strContent = strContent; if (pAttachment!= NULL) { m_pAttachment = new CAttachment(*pAttachment); } else { m_pAttachment = NULL; } } CWeeklyClone(CWeeklyClone& other) { // 拷贝构造函数需要注意深拷贝和浅拷贝的问题 m_strName = other.m_strName; m_strDate = other.m_strDate; m_strContent = other.m_strContent; if (other.m_pAttachment != NULL) { // 浅拷贝方式 // m_pAttachment = other.m_pAttachment; // 深拷贝方式 m_pAttachment = new CAttachment(*other.m_pAttachment); } else { m_pAttachment = NULL; } //cout << "CWeeklyClone CopyConstruct" << endl; } ~CWeeklyClone() { if (m_pAttachment != NULL) { delete m_pAttachment; m_pAttachment = NULL; } //cout << "CWeeklyLogClone Deonstruct" << endl; } CWeeklyClone* Clone() { return new CWeeklyClone(*this); } void SetName(string strName) { m_strName = strName; } void SetDate(string strDate) { m_strDate = strDate; } void SetContent(string strContent) { m_strContent = strContent; } void SetAttachment(CAttachment *pAttachment) { if (m_pAttachment != NULL) { m_pAttachment = pAttachment; } } CAttachment *GetAttachment() { return m_pAttachment; } void PrintWeekly() { cout << "start:------------M公司个人工作周报------------" << endl; cout << "周次:" << m_strDate <<endl; cout << "员工:" << m_strName <<endl; cout << "内容:" << m_strContent << endl; if (m_pAttachment != NULL) cout << "附件:" << m_pAttachment->m_strContent << endl; cout << "end:------------M公司个人工作周报------------" << endl; } private: string m_strName; string m_strDate; string m_strContent; CAttachment *m_pAttachment; };
2.3.3 附件类
#include <string> #include <iostream> using namespace std; // 附件类 class CAttachment { public: CAttachment(string strContent="") { m_strContent = strContent; //cout << "CAttachment CopyConstruct" << endl; } CAttachment(CAttachment &other) { m_strContent = other.m_strContent; //cout << "CAttachment CopyConstruct" << endl; } ~CAttachment() { //cout << "CAttachment Deonstruct" << endl; } public: string m_strContent; };
2.4 测试
#include "stdio.h" #include "prototype.h" void main() { // 创建附件 CAttachment *pAttachment = new CAttachment("附件:紫贝龙二号文件"); // 创建周报 CWeeklyClone *pWeekly1 = new CWeeklyClone("张林", "第一周", "ddcpy模块单元测试", pAttachment); pWeekly1->PrintWeekly(); CWeeklyClone *pWeekly2 = pWeekly1->Clone(); pWeekly2->SetDate("第二周"); pWeekly2->PrintWeekly(); // 判断周报是否相同 cout << "深拷贝模式下判断周报和附件是否相同:" << endl; pWeekly1==pWeekly2 ? (cout << "周报相同"<< endl):(cout << "周报不相同" << endl); pWeekly1->GetAttachment()==pWeekly2->GetAttachment() ? (cout << "附件相同"<< endl):(cout << "附件不相同" << endl); return; }
三 原型模式总结
3.1 主要优点
(1)当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高新实例的创建效率。
(2)可以使用深复制的方式保存对象的状态。将对象复制一份并将其状态保存起来,以便于在使用的时候使用,比如恢复到某一个历史状态,可以辅助实现撤销操作。
3.2 主要缺点
(1)需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
(2)为了支持深复制,当对象之间存在多重嵌套引用关系时,每一层对象都必须支持深复制,实现起来可能比较麻烦。
3.3 应用场景
最主要的应用场景就在于 创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或者网络资源),新的对象可以通过原型模式对已有对象进行复制来获得。如果是相似对象,则可以对其成员变量稍作修改。