C++设计模式——原型模式(Prototype Pattern)

C++设计模式——原型模式(Prototype Pattern)

微信公众号:幼儿园的学霸

目录

文章目录

  • 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类图比较简单,如下:
C++设计模式——原型模式(Prototype Pattern)_第1张图片

对象具有自我复制功能

代码示例

普通指针方式实现

#include 

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 s2(s1->clone());
    //对新对象进行set等操作
    s2->show();

    //不使用原型模式的写法
    //Sheep *pSheep3(new Sheep(*s1));//这种写法编译不通过
    //Clone *s3(new Clone(s1));//编译不通过
    Clone *s3(new Sheep(*dynamic_cast(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 

using std::cout;
using std::endl;
using std::string;


//
//原型模式 采用智能指针实现
//

//提供一个抽象克隆基类
class Clone {
public:
    virtual std::shared_ptr 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() override {
        return std::make_shared(*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(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()方法创建一个对象,然后由工厂提供给调用者



下面的是我的公众号二维码图片,欢迎关注。
图注:幼儿园的学霸

你可能感兴趣的:(C++,设计模式,c++)