1. 简介
原型模式(Prototype)属于对象创建型模式。其特点在于通过复制一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的原型。它提供了一种快速有效的创建对象的方式。当直接创建对象的代价比较大时可以采用这种方式。例如:一个对象需要在一个高代价的数据库操作之后被创建。当我们需要再次创建一个该对象的时候,就可以直接通过之前创建的原型通过复制来返回一个新实例,并在需要的时候更新数据库,以此来减少数据库调用。
原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。其实就是从一个对象再创建另一个可定制的对象,而且不需要知道任何创建的细节。
2. 结构
下面是原型模式的结构图:
- Prototype(抽象原型类):声明一个克隆自身的接口。
- ConcretePrototype(具体原型类):实现一个克隆自身的操作。
- Client(客户类):让一个原型克隆自身从而创建一个新的对象。
3. 浅克隆与深克隆
既然是通过原型对象克隆出另外一个新的对象,那么克隆对象时就会涉及是浅克隆还是深克隆。
3.1 浅克隆
浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原对象。
3.2 深拷贝
深拷贝:深拷贝把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
4. 实例分析
考虑这么一个应用场景:我们需要开发一个用于邮件发送的软件,由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),考虑到客户的使用方便,我们需要提供一个实用功能——邮件复制。因为,对于已经创建好的邮件对象,可以通过复制方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。
说了这么多,听起来和上面讲到的原型模式很相似。因此,考虑使用原型模式设计此功能。而附件的是否复制则取决于是否进行浅拷贝或深拷贝。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Prototype.h
*Author: Huangjh
*Version:
*Date: 2017-10-31
*Description: 原型类,声明一个克隆自身的接口
*Others:
**********************************************************************************/
#ifndef _PROTOTYPE_H
#define _PROTOTYPE_H
#include <iostream>
#include <string>
class CPrototype
{
public:
CPrototype() { }
virtual ~CPrototype() { }
virtual CPrototype *Clone() = 0;
void Display()
{
std::cout << "一封信邮件" << std::endl;
std::cout << "发送者:" << m_strSender << std::endl;
std::cout << "接收者:" << m_strReceiver << std::endl;
std::cout << "标 题:" << m_strTitle << std::endl;
std::cout << "内 容:" << m_strContent << std::endl;
std::cout << "日 期:" << m_strDate << std::endl;
}
void setSender(const std::string &strSender)
{
m_strSender = strSender;
}
void setReceiver(const std::string &strReceiver)
{
m_strReceiver = strReceiver;
}
void setTitle(const std::string &strTitle)
{
m_strTitle = strTitle;
}
void setContent(const std::string &strContent)
{
m_strContent = strContent;
}
void setDate(const std::string &strDate)
{
m_strDate = strDate;
}
void setAdjunct(void *pAdjunct)
{
m_pAdjunct = pAdjunct;
}
private:
std::string m_strSender;
std::string m_strReceiver;
std::string m_strTitle;
std::string m_strContent;
std::string m_strDate;
void *m_pAdjunct; //指代附件
};
#endif //#ifndef _PROTOTYPE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: ConcretePrototype.h
*Author: Huangjh
*Version:
*Date: 2017-10-31
*Description: 具体原型类,实现一个克隆自身的操作
*Others:
**********************************************************************************/
#ifndef _CONCRETE_PROTOTYPE_H
#define _CONCRETE_PROTOTYPE_H
#include "Prototype.h"
class CConcretePrototype : public CPrototype
{
public:
CConcretePrototype() { }
~CConcretePrototype() { }
CPrototype *Clone()
{
return new CConcretePrototype(*this);
}
};
#endif //#ifndef _CONCRETE_PROTOTYPE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-10-31
*Description: 原型模式测试用例
*Others:
**********************************************************************************/
#include "ConcretePrototype.h"
int main(void)
{
CPrototype *pPrototypeClass = new CConcretePrototype();
pPrototypeClass->setSender("何总");
pPrototypeClass->setReceiver("李四");
pPrototypeClass->setTitle("重要通知");
pPrototypeClass->setContent("由于临时的变更需要张三和李四去厦门出差!下面是出差的重要事项。。。");
pPrototypeClass->setDate("2017年11月1日20:50:22");
pPrototypeClass->setAdjunct(NULL);
pPrototypeClass->Display();
CPrototype *pCloneClass = pPrototypeClass->Clone();
pCloneClass->setReceiver("张三");
pCloneClass->Display();
return 0;
}
运行结果如下所示:一封信邮件
发送者:何总
接收者:李四
标 题:重要通知
内 容:由于临时的变更需要张三和李四去厦门出差!下面是出差的重要事项。。。
日 期:2017年11月1日20:50:22
一封信邮件
发送者:何总
接收者:张三
标 题:重要通知
内 容:由于临时的变更需要张三和李四去厦门出差!下面是出差的重要事项。。。
日 期:2017年11月1日20:50:22
对于一封已经创建好的邮件,我们通过克隆出一个新实例,然后按需要修改就 可以了。这边例子没有体现出深拷贝和浅拷贝。5. 原型管理器
当一个系统中原型数目不固定时(也就是说它们可以动态的创建和销毁),要保持一个可用的注册表。客户不会自己来管理原型,但会在注册表中存储和检索原型。客户在克隆一个原型前会向注册表请求该原型。我们称这个注册表为原型管理器。
原型管理器是一个关联存储器,它返回一个与给定关键字相匹配的原型。它有一些操作可以用来通过关键字注册原型和解除原型。客户可以在运行时更改甚至浏览这个注册表、这使得客户无需编写代码就可以扩展并得到系统清单。
下面我们给出使用原型管理器的例子:
/*********************************************************************************
*Copyright(C),Your Company
*FileName: PrototypeMng.h
*Author: Huangjh
*Version:
*Date: 2017-10-31
*Description: 原型管理器
*Others:
**********************************************************************************/
#ifndef _PROTOTYPE_MNG_H
#define _PROTOTYPE_MNG_H
#include <map>
class CPrototype;
class CPrototypeMng
{
public:
CPrototypeMng() { }
~CPrototypeMng() { }
void AddPrototype(const std::string &strKey, CPrototype *pPrototype)
{
MapPrototype::iterator iter = m_mapPrototypePair.find(strKey);
if (iter != m_mapPrototypePair.end())
return;
m_mapPrototypePair[strKey] = pPrototype;
}
void DelPrototype(const std::string &strKey, CPrototype *pPrototype)
{
MapPrototype::iterator iter = m_mapPrototypePair.find(strKey);
if (iter == m_mapPrototypePair.end())
return;
m_mapPrototypePair.erase(iter);
}
CPrototype *GetPrototype(const std::string &strKey)
{
MapPrototype::iterator iter = m_mapPrototypePair.find(strKey);
if (iter == m_mapPrototypePair.end())
return NULL;
return iter->second;
}
private:
typedef std::map<std::string, CPrototype*> MapPrototype;
MapPrototype m_mapPrototypePair;
};
#endif //#ifndef _PROTOTYPE_MNG_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-10-31
*Description: 原型模式测试用例
*Others:
**********************************************************************************/
#include "ConcretePrototype.h"
#include "PrototypeMng.h"
int main(void)
{
CPrototypeMng *pPrototypeMng = new CPrototypeMng();
CPrototype *pPrototypeClass = new CConcretePrototype();
//添加到原型管理器中去
pPrototypeMng->AddPrototype("Mail", pPrototypeClass);
pPrototypeClass->setSender("何总");
pPrototypeClass->setReceiver("李四");
pPrototypeClass->setTitle("重要通知");
pPrototypeClass->setContent("由于临时的变更需要张三和李四去厦门出差!下面是出差的重要事项。。。");
pPrototypeClass->setDate("2017年11月1日20:50:22");
pPrototypeClass->setAdjunct(NULL);
pPrototypeClass->Display();
//从原型管理器中取出
CPrototype *pCloneClass = pPrototypeMng->GetPrototype("Mail")->Clone();
pCloneClass->setReceiver("张三");
pCloneClass->Display();
return 0;
}
6. 优缺点
6.1 优点
- 它对客户隐藏了具体的产品类,因此减少了客户知道的名字和数目。并且使的客户无需改变即可使用与特定应用相关的类。
- 运行时刻增加和删除产品。Prototype允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。
- 改变值以指定新对象。高度动态的系统允许你通过对象复合定义新的行为——例如,通过为一个对象变量指定值——并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。
- 改变结构以指定新对象。许多应用由部件和子部件来创建。这样的应用通常允许你实例化复杂的、用户定义的结构,将这个复杂的、用户定义的结构实现一个深拷贝的Clone,那么这个复杂的、用户定义的结构就是原型。
- 减少子类的构造。
- 用类动态配置应用。一个希望创建动态载入类的实例的应用不能静态引用类的构造器。而应该由运行环境在载入是自动创建每个类的实例。并用原型管理器来注册这个实例。
6.2 缺点
- Prototype的主要缺点是每一个Prototype的子类都必须实现Clone操作,这可能很困难。例如,当所考虑的类已经存在时就难以新增Clone操作。当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难的。
7. 适用性
当一个系统应该独立于它的产品创建、构成和表示时,要使用Prototype模式;以及
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者
- 为了避免创建一个与产品类层次平行的工厂类层次时;或者
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它可能比每次用合适的状态手工实例化该类更方便一些。
8. 参考资料
- 《大话设计模式》
- 《设计模式——可复用面向对象软件的基础》