C++ 模式设计 原型模式(深拷贝/克隆)


思考: 原型模式跟装饰器模式的区别?

本文核心:

	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;
}
设计并实现一个动态整型数组类Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。 设计并实现一个动态整型数组类Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落子无悔!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值