当创建给定类的实例的过程很昂贵或很复杂时,就使用原型模式。
原型模式的核心思想是,通过拷贝指定的“原型实例(对象)”,创建跟该对象一样的新对象。简单理解就是“克隆指定对象”。
这里提到的“原型实例(对象)”,就是被克隆的对象,它的作用就是指定要创建的对象种类。
1、原型模式的优点
性能优良,原型模式是在内存二进制流的拷贝,当new的对象比较复杂的时候,要比直接new一个对象性能好很多,特别是在一个循环体内产生大量的对象时,原型模式可以更好的体现其优点。
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
对客户隐藏制造新实例的复杂性
2、原型模式的缺点
由于是直接在内存中拷贝,构造函数不会执行。
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候,耦合度较高。
3、适合使用原型模式的场景及注意事项
当通过初始化对象时需要消耗非常多的资源的时候,包括软硬件资源
当通过new 一个对象需要准备繁琐的数据或复杂的权限时
当一个对象需要提供给其他对象访问的时候,而且各个调用者可能会改变其属性的时候可以考虑使用原型模式构造多个副本对象供其他对象调用,又称为保护性拷贝。
使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。即final修饰的不会被拷贝。
案例:
找工作时,需要准备简历。假如没有打印设备,需手写简历,其简历内容都是完全相同的内容。这样有个缺陷,如果要修改简历中的某项内容(比如对于通信公司的简历想突出强调开发过4G xxx项目,5G xxx项目,对于C++要求高的公司想强调“精通”STL和设计模式。。。),那么所有已写好的简历都要修改,工作量很大。随着科技的进步,出现了打印设备。我们只需手写一份,然后利用打印设备复印多份即可。如果要修改简历中的某项,那么修改原始的版本就可以了,然后再复印。原始的那份手写稿相当于是一个原型,有了它,就可以基于原型通过复印(拷贝:深拷贝或浅拷贝)创造出更多的新简历。这就是原型模式的基本思想。如果不基于原型,就要重新构造出一份简历,过程复杂繁琐。
类图:
实现如下:
resume.h:
#ifndef RESUME_H
#define RESUME_H
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Resume {
public:
void setName(string name)
{
m_name = name;
}
string getName()
{
return m_name;
}
void setSkill(string skill)
{
m_skill = skill;
}
string getSkill()
{
return m_skill;
}
void display()
{
cout << "简历人姓名:" << m_name
<< " ,技能:" << m_skill << endl;
}
virtual Resume* clone() = 0;
protected:
string m_name;
string m_skill;
};
class ResumeA :public Resume {
public:
ResumeA(string name, string skill)
{
m_name = name;
m_skill = skill;
}
virtual Resume* clone() override
{
return this;
}
};
#endif // RESUME_H
main.cpp:
/*
* 原型模式
*
* date:2023-9-21
*/
#include "resume.h"
int main()
{
cout << "原始简历:" << endl;
Resume* r1 = new ResumeA("小A", "熟悉C++");
r1->display();
cout << "\n基于原始简历修改的新简历(浅拷贝):" << endl;
//由于是直接在内存中拷贝,构造函数不会执行。
auto r2 = r1->clone();
r2->setSkill("精通C++ STL");
r2->display();
}
运行结果: