1.作用
可以对对象进行动态隐藏的复制。例如在程序中需要生成某个状态下对象的副本时可以使用该模式。 有人会说利用类的拷贝构造函数也可以达到这样是效果,的确如此,但拷贝构造达不到隐藏的效果。
四人书中定义的意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
核心: 用原型实例指定创建对象的种类。
利用拷贝构造函数拷贝对象时,我们必须提供待被复制具体的对象,确切的说我们必须知道具体的实例,例如:
2.UML视图
- Prototype(抽象原型):原型基类,主要是为了定义了克隆自身的接口。
- ConcretePrototype(具体原型):被复制的对象,需要实现 Prototype 定义的接口,即拷贝构造函数。
- Client(用户):用户利用原型接口得到拷贝对象。
3.实现
举例:动物园有一只高端山羊,今年2岁了,研究所需要对这个年龄的这种羊展开研究,这个任务落到你头上了,假设时间不限,方案有如下三个:
- 从动物抱一只此山羊的羔羊,培养两年交货。—new 一个具体对象,再进行赋值。
- 从动物把这只羊迁到某魔术中心变一只一模一样的(假设可以)。—利用对象拷贝构造函数创建具体对象,需要提供具体实例。
- 割一块此山羊的肉交给生物学家,让他们克隆一只。—原型模式创建对象,只提供原型接口(肉)就可拷贝具体对象,不需要提供具体实例。
代码实现
// PrototypePattern.cpp : 定义控制台应用程序的入口点。
//
/******C++设计模式之原型模式********************/
#include <iostream>
#include <string>
using namespace std;
//Prototype
class Animal
{
public:
Animal(){}
virtual ~Animal(){}
virtual Animal* clone() = 0;
void show()
{
cout << "我是名为" << m_Name << "的山羊,年龄为" << m_Age << "岁" << endl;
}
protected:
int m_Age = 2;
string m_Name;
};
//ConcretePrototype
class Sheep : public Animal
{
public:
Sheep(string &name){ m_Name = name; }
~Sheep(){}
Sheep(const Sheep& other);
Animal* clone();
};
Sheep::Sheep(const Sheep& other)//拷贝构造
{
this->m_Age = other.m_Age;
this->m_Name = other.m_Name;
}
Animal* Sheep::clone()//利用拷贝构造克隆
{
return new Sheep(*this);
}
//Client
int main()
{
Animal *animal = new Sheep(string("狗蛋"));
animal->show();
Animal *CloneAnimal = animal->clone();//克隆山羊
CloneAnimal -> show();
return 0;
}
结果:
我是名为狗蛋的山羊,年龄为2岁
我是名为狗蛋的山羊,年龄为2岁
请键继续 .
4. 注意事项
说到拷贝构造函数就绕不开浅拷贝和深拷贝,二者区别在于是否重新开辟存储空间来存储依靠new创建的成员对象。 具体区别可自行百度。
5.总结
- 优点:
如果创建新的对象比较复杂,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
简化对象的创建,无需理会创建过程。
可以在程序运行时(对象属性发生了变化)获得一份内容相同的实例,他们之间不会相互干扰。
- 缺点:
在实现深拷贝时可能需要比较复杂的代码
需要为每一个类配备一个克隆方法,而且该克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。 - 适用场景
如果创建新对象成本较大,可以利用已有的对象进行复制来获得。
如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
- 缺点: