思考: 原型模式跟装饰器模式的区别?
本文核心:
WorkExperience* clone(){
WorkExperience *w = new WorkExperience();
*w = *this; // 深拷贝一个相同的自己, 注意如果this中有指针, 需要在对这个指针类进行clone方法的实现, 这样层层嵌套, 就实现了整体的深拷贝
return w; // 将当前的this深拷贝给w, 返回
}
1. 理论知识
当遇到需要复制的实例时, 我们就用到了原型模式, 比如打印20份自己的简历, 一个自己的简历类, 然后实例化一个简历A后, 在实例化 B=A, 但是很抱歉, 这里的B=A其实B是A的引用, 并不是真实的实例, 所以需要用到克隆技术
原型模式: 用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象; 其实就是从一个对象在创建另外一个可定制的对象, 并且不需要知道创建的任何细节;
**使用场景: **
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。
原型模式的优缺点
原型模式的优点: 底层的二进制实现拷贝,相比起new操作可以节约性能
原型模式的缺点: 构造函数没有调用,牺牲了灵活性
2. 逻辑模板代码
// 原型模式起始就是从一个对象在创建另一个可定制的对象, 而且不需要知道任何创建细节
// 原型类
class Prototype{
private:
string id;
public:
Prototype(string id): id(id){}
Prototype() : id("ndy"){}
string get(){
return id;
}
virtual Prototype* clone() = 0; // 这个方法是关键
};
// 具体原型类
class ConcretePrototype_1 : public Prototype{
private:
public:
ConcretePrototype_1(string id): Prototype(id){}
ConcretePrototype_1() {}
// 相当于实现了深拷贝
virtual Prototype *clone(){
// 先开辟等量空间
ConcretePrototype_1 *p = new ConcretePrototype_1();
*p = *this; // 在将当前类深拷贝给上面空间
return p;
}
};
// 客户端
int main(int argc, char const *argv[]){
Prototype *p1 = new ConcretePrototype_1("nb");
Prototype *c1 = p1->clone();
// ConcretePrototype_1 *p1 = new ConcretePrototype_1("nb");
// ConcretePrototype_1 *c1 = (ConcretePrototype_1 *)p1->clone();
cout << c1->get() <<endl; // 输出nb
delete p1, c1;
return 0;
}
3. 应用
3.1 应用1: 改写简历
创建一个简历实例A, 在从这个实例A中修改数据变成实例B
// 简历
class Resume{
private:
string name, sex, age, timeArea, company;
public:
Resume(string name) : name(name){}
// 设置个人信息
void setPersonalInfo(string sex, string age) {
this->sex = sex;
this->age = age;
}
// 设置工作经历
void setWorkExperience(string timeArea, string company){
this->company = company;
this->timeArea = timeArea;
}
// 显示
void Display(){
cout << "个人信息:" << name << " " << age << " " << sex << " " << timeArea << " " << company <<endl;
}
Resume* clone(){
Resume *r = new Resume("");
*r = *this;
return r;
}
};
// 这里实现了一个功能为除了name不变, 其他的信息可以改变的不同的简历
// 比如我做了一个简历V1, 然后简历V2是在V1的基础上修改了些信息做的
int main(int argc, char const *argv[])
{
Resume *nb = new Resume("nb");
nb->setPersonalInfo("男", "25");
nb->setWorkExperience("2015-2-2", "tencent");
Resume *b2 = (Resume*) nb->clone();
b2->setPersonalInfo("女" , "27");
Resume *b3 = (Resume*) b2->clone();
b3->setWorkExperience("2020-5-5", "baidu");
b2->Display();
nb->Display();
b3->Display();
delete nb, b2, b3;
return 0;
}
3.2 深浅拷贝的问题 *
上面应用遇到一个问题, 当成员变量有指针时, 默认拷贝还是浅拷贝, 比如工作经历如果是一个类, 那么实例指针只会在拷贝的时候拷贝指针, 而不会拷贝实例, 这样拷贝的指针依然指向实例未变, 如果看不懂, 请看下面的例子
// 工作经历类
class WorkExperience{
private:
string workDate, company;
public:
void set(string workDate, string company){
this->workDate = workDate, this->company = company;
}
string get_workDate(){return this->workDate;}
string get_company(){return this->company;}
};
// 简历
class Resume{
private:
string name, sex, age;
WorkExperience *work = nullptr;
public:
Resume(string name) : name(name), work(new WorkExperience()){}
// 设置个人信息
void setPersonalInfo(string sex, string age) {
this->sex = sex;
this->age = age;
}
// 设置工作经历
void setWorkExperience(string timeArea, string company){
work->set(timeArea, company);
}
// 显示
void Display(){
cout << "个人信息:" << name << " " << age <<" " << sex << " " << work->get_workDate() << " " << work->get_company() <<endl;
}
Resume* clone(){
Resume *r = new Resume("");
*r = *this;
// 注意: 这里由于work是类, 默认拷贝是引用, 因此这里进行拷贝,
// 虽然其他成员变量都是深拷贝, 但是work这个指针也是拷贝了指针, 并没有拷贝指针所指的对象
return r;
}
};
// 这里实现了一个功能为除了name不变, 其他的信息可以改变的不同的简历
// 比如我做了一个简历V1, 然后简历V2是在V1的基础上修改了些信息做的
int main(int argc, char const *argv[])
{
Resume *nb = new Resume("nb");
nb->setPersonalInfo("男", "25");
nb->setWorkExperience("2015-2-2", "tencent");
Resume *b2 = (Resume*) nb->clone();
b2->setPersonalInfo("女" , "27");
Resume *b3 = (Resume*) b2->clone();
b3->setWorkExperience("2020-5-5", "baidu");
b2->Display();
nb->Display();
b3->Display();
delete nb, b2, b3;
return 0;
}
个人信息:nb 27 女 2020-5-5 baidu
个人信息:nb 25 男 2020-5-5 baidu
个人信息:nb 27 女 2020-5-5 baidu
就会发现所有的工作经验都变成最后修改的值了, 这是因为浅拷贝, 无论怎么修改都是在修改第一个实例的工作经验
3.3 深拷贝的嵌套使用
在类里面都增加一个clone方法来克隆(深拷贝)对象, 这样就方便了下面使用该类进行深拷贝的操作了
// 工作经历类
class WorkExperience{
private:
string workDate, company;
public:
void set(string workDate, string company){
this->workDate = workDate, this->company = company;
}
string get_workDate(){return this->workDate;}
string get_company(){return this->company;}
WorkExperience* clone(){ // b. 当调用这个方法时, 深拷贝了一个新work
WorkExperience *w = new WorkExperience();
*w = *this;
return w; // 将当前的this深拷贝给w, 返回
}
};
// 简历
class Resume{
private:
string name, sex, age;
WorkExperience *work = nullptr;
public:
Resume(string name) : name(name), work(new WorkExperience()){} // a. 第一次创建简历时, 同时创建了工作经历类
Resume(){}
~Resume(){ // 由于每个resume都对应一个work
if(work != nullptr){
delete work
}
}
// 设置个人信息
void setPersonalInfo(string sex, string age) {
this->sex = sex;
this->age = age;
}
// 设置工作经历
void setWorkExperience(string timeArea, string company){
work->set(timeArea, company);
}
// 显示
void Display(){
cout << "个人信息:" << name << " " << age <<" " << sex << " " << work->get_workDate() << " " << work->get_company() <<endl;
}
Resume* clone(){
Resume *r = new Resume(); // c. 创建干净的简历时, 此时r->work还是空的
*r = *this; // 此时r->work已经是this->work的深拷贝了, 但是里面的work指针还是指向this的work实例
r->work = work->clone(); // 这样就深拷贝了一个新的work给r, 在返回r, 就是全新的了
return r;
}
};
// 这里实现了一个功能为除了name不变, 其他的信息可以改变的不同的简历
// 比如我做了一个简历V1, 然后简历V2是在V1的基础上修改了些信息做的
int main(int argc, char const *argv[])
{
Resume *nb = new Resume("nb");
nb->setPersonalInfo("男", "25");
nb->setWorkExperience("2015-2-2", "tencent");
Resume *b2 = (Resume*) nb->clone();
b2->setPersonalInfo("女" , "27");
Resume *b3 = (Resume*) b2->clone();
b3->setWorkExperience("2020-5-5", "baidu");
b2->Display();
nb->Display();
b3->Display();
delete nb, b2, b3;
return 0;
}