C++设计模式——原型模式(Prototype Pattern)
微信公众号:幼儿园的学霸
目录
定义
Specify the kinds of Objects to create using a prototypical instance, and create new objects by copying this prototype.
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式是一种创建型模式,允许一对象再创建另外一个可定制的对象,而无需知道任何创建的细节。
通过复制(克隆、拷贝)一个指定类型的对象来创建更多同类型的对象。这个指定的对象可被称为“原型”对象,也就是通过复制原型对象来得到更多同类型的对象。
学过C++的都知道拷贝构造函数,复制一个对象分为浅拷贝和深拷贝。
- 浅拷贝:就是给对象中的每个成员变量进行复制,就是把A1类中的变量直接赋给A2类中变量,属于值传递,但是涉及到有new之类内存分配的地方,他们却是共享内存的。
- 深拷贝:就是不仅使用值传递,而是要每个变量都有自己一份独立的内存空间,互不干扰。
默认的拷贝构造函数是浅拷贝的,如果要实现深拷贝,就需要重写拷贝构造函数T(const T&)
。
而原型模式的实质就是实现对于原型的拷贝。主要是通过拷贝构造函数来实现。其类结构图也比较简单,拷贝构造函数来确定clone的时候是深拷贝还是浅拷贝。
原型模式中涉及的角色有:
- 抽象原型(Prototype)角色:给出所有具体原型类所需的接口,声明具备clone能力
- 具体原型(ConcretePrototype)角色:被复制的对象/具体的原型类,实现抽象原型类的clone()方法
- 客户端(Client):提出创建对象的请求,使用具体原型类中的 clone() 方法来复制新的对象
其UML类图比较简单,如下:
对象具有自我复制功能
代码示例
普通指针方式实现
#include <bits/stdc++.h>
using std::cout;
using std::endl;
using std::string;
//
//原型模式
//关键代码:return new className(*this)
//提供一个抽象克隆基类
class Clone {
public:
virtual Clone *clone() = 0;
virtual ~Clone() = default;//将析构函数声明为虚析构函数,否则存在内存泄露问题
virtual void show() = 0;
};
//具体的实现类
class Sheep : public Clone {
public:
Sheep(int id, std::string name)
: Clone(), m_id(id), m_name(name) {
std::cout << "Sheep() address:" << this << endl;
cout << "Sheep() id address:" << &m_id << endl;
cout << "Sheep() name address:" << &m_name << endl;
}
~Sheep() {
std::cout << "Destructor called!" << std::endl;
}
//关键代码拷贝构造函数
Sheep(const Sheep &obj) {
this->m_id = obj.m_id;
this->m_name = obj.m_name;
std::cout << "Sheep(const Sheep& obj) address:" << this << endl;
cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl;
cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl;
}
//关键代码克隆函数,返回return new Sheep(*this)
Clone *clone() override {
return new Sheep(*this);//注意观察拷贝构造函数的实现,采用了深拷贝
}
void show() override {
cout << "id :" << m_id << endl;
cout << "name:" << m_name.data() << endl;
}
private:
int m_id;
string m_name;
};
int main() {
Clone *s1 = new Sheep(1, "abs");
s1->show();
// delete s1;
// s1 = nullptr;
// return 0;
//采用原型模式写法
Clone *s2 = s1->clone();
//std::shared_ptr<Clone> s2(s1->clone());
//对新对象进行set等操作
s2->show();
//不使用原型模式的写法
//Sheep *pSheep3(new Sheep(*s1));//这种写法编译不通过
//Clone *s3(new Clone(s1));//编译不通过
Clone *s3(new Sheep(*dynamic_cast<Sheep *>(s1)));
//对新对象进行set等操作
s3->show();
delete s1;
s1 = nullptr;
delete s2;
s2 = nullptr;
delete s3;
s3 = nullptr;
return 0;
//运行结果:
//Sheep() address:0x556bf9c71e70
//Sheep() id address:0x556bf9c71e78
//Sheep() name address:0x556bf9c71e80
//id :1
//name:abs
//Sheep(const Sheep& obj) address:0x556bf9c722c0
//Sheep(const Sheep& obj) id address:0x556bf9c722c8
//Sheep(const Sheep& obj) name address:0x556bf9c722d0
//id :1
//name:abs
//Sheep(const Sheep& obj) address:0x556bf9c72300
//Sheep(const Sheep& obj) id address:0x556bf9c72308
//Sheep(const Sheep& obj) name address:0x556bf9c72310
//id :1
//name:abs
//0x556bf9c71e70 Destructor called!
//0x556bf9c722c0 Destructor called!
//0x556bf9c72300 Destructor called!
}
在代码中注意将基的析构函数定义为虚函数,这样才能保证不会发生内存泄露。从运行结果也可以看到,new
的指针在delete
时都都调用析构函数了。
感兴趣的可以尝试将基类的虚析构函数注释掉,然后看下运行结果有什么不同。
智能指针方式实现
#include <bits/stdc++.h>
using std::cout;
using std::endl;
using std::string;
//
//原型模式 采用智能指针实现
//
//提供一个抽象克隆基类
class Clone {
public:
virtual std::shared_ptr<Clone> clone() = 0;
virtual ~Clone() = default;//将析构函数声明为虚析构函数,否则存在内存泄露问题
virtual void show() = 0;
};
//具体的实现类
class Sheep : public Clone {
public:
Sheep(int id, std::string name)
: Clone(), m_id(id), m_name(name) {
std::cout << "Sheep() address:" << this << endl;
cout << "Sheep() id address:" << &m_id << endl;
cout << "Sheep() name address:" << &m_name << endl;
}
~Sheep() {
std::cout << this << " Destructor called!" << std::endl;
}
//关键代码拷贝构造函数
Sheep(const Sheep &obj) {
this->m_id = obj.m_id;
this->m_name = obj.m_name;
std::cout << "Sheep(const Sheep& obj) address:" << this << endl;
cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl;
cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl;
}
//关键代码克隆函数,返回return new Sheep(*this)
std::shared_ptr<Clone> clone() override {
return std::make_shared<Sheep>(*this);//注意观察拷贝构造函数的实现,采用了深拷贝
//return new Sheep(*this);//注意观察拷贝构造函数的实现,采用了深拷贝
}
void show() override {
cout << "id :" << m_id << endl;
cout << "name:" << m_name.data() << endl;
}
private:
int m_id;
string m_name;
};
int main(){
auto s1 = std::make_shared<Sheep>(1, "abs");
s1->show();
//采用原型模式写法
auto s2 = s1->clone();
//auto s2(s1->clone());
//对新对象进行set等操作
s2->show();
return 0;
//运行结果:
//Sheep() address:0x560e46a92e80
//Sheep() id address:0x560e46a92e88
//Sheep() name address:0x560e46a92e90
//id :1
//name:abs
//Sheep(const Sheep& obj) address:0x560e46a932e0
//Sheep(const Sheep& obj) id address:0x560e46a932e8
//Sheep(const Sheep& obj) name address:0x560e46a932f0
//id :1
//name:abs
//0x560e46a932e0 Destructor called!
//0x560e46a92e80 Destructor called!
}
总结
为什么需要原型模式
- 为什么不用new直接新建对象,而要用原型模式?
首先,用new新建对象不能获取当前对象运行时的状态,其次就算new了新对象,在将当前对象的值复制给新对象,效率也不如原型模式高。 - 为什么不直接使用拷贝构造函数,而要使用原型模式?
原型模式与拷贝构造函数是不同的概念,拷贝构造函数涉及的类是已知的,原型模式涉及的类可以是未知的(基类的拷贝构造函数只能复制得到基类的对象)。原型模式生成的新对象可能是一个派生类。拷贝构造函数生成的新对象只能是类本身。原型模式是描述了一个通用方法(或概念),它不管是如何实现的,而拷贝构造则是描述了一个具体实现方法。
优缺点
- 优点
1.如果创建新的对象比较复杂,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2.简化对象的创建,无需理会创建过程。
3.可以在程序运行时(对象属性发生了变化)获得一份内容相同的实例,他们之间不会相互干扰。关于第1点效率高的说明:原型模式是内存二进制流的复制,要比直接new 一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点
- 缺点
1.在实现深拷贝时可能需要比较复杂的代码
2.需要为每一个类配备一个克隆方法,而且该克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂提供给调用者