原型模式(创建型)

前言


在公司上班时候,每周都需要写工作周报,而写周报的时候,每次的格式都差不多,有时候想偷懒,就想自己弄个周报的模板,然后每次填充模板的内容,这样既提高了工作效率,又保证了格式的一致性,上级领导每次浏览周报的时候也一目了然了,真是一石二鸟–),这种可以看成原型模式的应用,即每次周报对象内容都大概相同,仅需要复制粘贴,然后修改下每周的差异内容即可。

原型模式


原型模式,顾名思义,就是有个对象的原型,然后再通过此原型对象来创建新的对象。而这些新的对象,可以内容相同,也可以不同。

意图

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

参与者

  • AbstractPrototype
    抽象原型类,声明一个克隆自身的接口

  • ConcretePrototype
    具体原型类,实现一个克隆自身的操作

  • Client
    让一个原型克隆自身从而创建一个新的对象

模式结构

prototype

代码实现

  1. 首先声明一个抽象的原型类AbstractPrototype,原型类中必须有clone()接口(虚函数):

    1
    2
    3
    4
    5
    6
    7
    class AbstractPrototype
    {
    public:
    AbstractPrototype() {};
    virtual ~AbstractPrototype() {};
    virtual AbstractPrototype* Clone() = 0; // 克隆接口,返回自身的类型
    };
  2. 然后分别实现两个具体的原型对象,实现其中的clone()接口(一般通过拷贝构造函数来实现):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    // 具体原型对象1
    class Prototype1: public AbstractPrototype
    {
    private:
    int m_ValueTest; //浅拷贝 测试值
    public:
    Prototype1(int value): m_ValueTest(value) {};
    Prototype1(const Prototype1& rhs) // 拷贝构造函数
    {
    m_ValueTest = rhs.m_ValueTest;
    }
    Prototype1* Clone() // 返回自己类类型
    {
    return new Prototype1(*this); // 通过调用拷贝构造函数来Clone对象
    }
    void ShowValue()
    {
    cout << m_ValueTest << endl;
    }
    };

    class Prototype2: public AbstractPrototype
    {
    private:
    int m_ValueTest; // 浅拷贝,一般的拷贝
    char *m_pName; // 涉及到深拷贝
    public:
    Prototype2(int value, const char* pname)
    {
    m_ValueTest = value;
    if (NULL == pname)
    {
    m_pName = new char[1];
    m_pName[0] = '\0';
    }
    else
    {
    m_pName = new char[strlen(pname) + 1];
    strcpy(m_pName, pname);
    }
    };
    virtual ~Prototype2()
    {
    delete m_pName; // 释放内存
    }
    Prototype2(const Prototype2& rhs) // 拷贝构造函数
    {
    //浅拷贝:只会拷贝对象中的基本的数据类型
    m_ValueTest = rhs.m_ValueTest;

    //深拷贝:需要手动申请内存,对于数组、容器对象、引用(指针)对象
    m_pName = new char[strlen(rhs.m_pName) + 1];
    strcpy(m_pName, rhs.m_pName);
    }
    Prototype2* Clone() // 返回自己类类型
    {
    // 通过调用拷贝构造函数来Clone对象
    Prototype2 *pP2 = new Prototype2(*this);
    pP2->m_ValueTest++; //改变克隆的对象内部成员,克隆出不同m_ValueTest的对象
    return pP2;
    }
    void ShowValue()
    {
    cout << m_ValueTest << endl;
    cout << m_pName << endl;
    }
    };
  3. 测试原型模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void PrototypeTest()
    {
    // 返回子类型的类指针,抽象类没有ShowValue方法
    Prototype1 *pP1= new Prototype1(10);

    Prototype1 *pPcopy= pP1->Clone(); // 克隆出新对象pPcopy
    pP1->ShowValue(); // 10
    pPcopy->ShowValue(); // 10

    Prototype2 *pP2= new Prototype2(20, "Tly");
    Prototype2 *pPcopy2= pP2->Clone();
    pP2->ShowValue(); // 20, "Tly"
    pPcopy2->ShowValue(); // 21 "Tly"

    delete pP1; pP1 = NULL;
    delete pPcopy; pPcopy = NULL;
    delete pP2; pP2 = NULL;
    delete pPcopy2; pPcopy2 = NULL;
    };

运行结果:

10
10
20
Tly
21
Tly

使用场景

  • 创建新对象成本较大,如初始化需要占用较长的时间、内存资源、网络资源等等,新的对象可以通过已有对象的复制得来,如是相似,则更改相应的属性成员变量即可
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现
  • 当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些
  • 重复地创建相似对象时可以考虑使用原型模式
  • 创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程

优缺点

  • 优点
    • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
    • 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响
    • 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作
  • 缺点
    • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
    • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦

与其他创建型模型区别

  • 工厂方法模式、抽象工厂模式、建造者模式和原型模式都是创建型模式
  • 工厂方法模式适用于生产较复杂,一个工厂生产单一的一种产品的时候
  • 抽象工厂模式适用于一个工厂生产多个相互依赖的产品;
  • 建造者模式着重于复杂对象的一步一步创建,组装产品的过程,并在创建的过程中,可以控制每一个简单对象的创建;
  • 原型模式则更强调的是从自身复制自己,创建要给和自己一模一样的对象

原型模式具体实例


实现前言所描述的简单工作周报和工作月报原型,增加一个原型管理器来管理这两种原型

代码实现

  1. 声明一个抽象的原型类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 抽象原型
    class AbstractReport
    {
    public:
    AbstractReport(){};
    virtual ~AbstractReport() {};
    public:
    virtual AbstractReport* Clone() = 0; // 抽象克隆方法
    virtual void display(void) {}; //用来显示
    virtual void setReportTitle(string title) {}; //用来修改title
    };
  2. 分别实现两个具体的原型类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    // 周报具体原型
    class WeeklyReport : public AbstractReport
    {
    private:
    string _reportTitle;
    int _weekNo;
    string _name;
    string _description;
    char* _accessory;
    public:
    WeeklyReport(string title, int no, string name, string description, \
    const char* accessory): _reportTitle(title), _weekNo(no), _name(name), _description(description)
    {
    if (NULL == accessory)
    {
    _accessory = new char[0];
    _accessory[0] = '\0';
    }
    else
    {
    _accessory = new char[strlen(accessory) + 1];
    strcpy(_accessory, accessory);
    }
    }
    virtual ~WeeklyReport()
    {
    if (NULL != _accessory)
    {
    delete _accessory;
    _accessory = NULL;
    }
    }
    public:
    WeeklyReport(WeeklyReport &rhs) //拷贝构造函数,浅拷贝 + 深拷贝
    {
    _reportTitle = rhs._reportTitle;
    _weekNo = rhs._weekNo;
    _name = rhs._name;
    _description = rhs._description;
    _accessory = new char[strlen(rhs._accessory) + 1]; // 深拷贝
    memmove(_accessory, rhs._accessory, (strlen(rhs._accessory) + 1));
    }

    WeeklyReport* Clone() // 克隆接口
    {
    WeeklyReport *report = new WeeklyReport(*this);
    return report;
    }

    void setReportTitle(string reportTitle)
    {
    _reportTitle = reportTitle;
    }

    void display(void)
    {
    cout << "weekly report title: " << _reportTitle << endl;
    cout << "weekly no: " << _weekNo << endl;
    cout << "reporter: " << _name << endl;
    cout << "description: " << _description <<endl;
    cout << "accessory: " << _accessory <<endl;
    }
    };

    // 月报具体原型
    class MonthlyReport : public AbstractReport
    {
    private:
    string _reportTitle;
    int _monthNo;
    string _name;
    string _description;
    char* _accessory;
    public:
    MonthlyReport(string title, int no, string name, string description, \
    const char* accessory): _reportTitle(title), _monthNo(no), _name(name), _description(description)
    {
    if (NULL == accessory)
    {
    _accessory = new char[0];
    _accessory[0] = '\0';
    }
    else
    {
    _accessory = new char[strlen(accessory) + 1];
    strcpy(_accessory, accessory);
    }
    }
    virtual ~MonthlyReport()
    {
    if (NULL != _accessory)
    {
    delete _accessory;
    _accessory = NULL;
    }
    }
    public:
    MonthlyReport(MonthlyReport &rhs) //拷贝构造函数,浅拷贝 + 深拷贝
    {
    _reportTitle = rhs._reportTitle;
    _monthNo = rhs._monthNo;
    _name = rhs._name;
    _description = rhs._description;
    _accessory = new char[strlen(rhs._accessory) + 1]; // 深拷贝
    memmove(_accessory, rhs._accessory, (strlen(rhs._accessory) + 1));
    }

    MonthlyReport* Clone() // 克隆接口
    {
    MonthlyReport *report = new MonthlyReport(*this);
    return report;
    }

    void setReportTitle(string reportTitle)
    {
    _reportTitle = reportTitle;
    }

    void display(void)
    {
    cout << "monthly report title: " << _reportTitle <<endl;
    cout << "monthly no: " << _monthNo <<endl;
    cout << "reporter: " << _name <<endl;
    cout << "description: " << _description <<endl;
    cout << "accessory: " << _accessory <<endl;
    }
    };
  3. 定义一个原型管理器类,使用单例模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // Report Manager
    class ReportManager
    {
    private:
    map<string, AbstractReport*> reportMap;
    private:
    ReportManager(){};
    ReportManager(const ReportManager &rhs) {};
    ReportManager& operator = (const ReportManager &rhs) {};
    public:
    static ReportManager* getInstance(void)
    {
    static ReportManager reportMgr;
    return &reportMgr;
    }
    public:
    // 增加新的report
    void addReport(string name, AbstractReport* report)
    {
    reportMap.insert(make_pair(name, report));
    }
    AbstractReport* getReport(string name) // 返回一个report副本
    {
    for (map<string, AbstractReport*>::iterator it = reportMap.begin(); \
    it != reportMap.end(); ++it)
    {
    if (it->first == name)
    {
    return (it->second)->Clone(); // clone方法返回副本
    }
    }

    return NULL;
    }
    };
  4. 测试实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void ReportPrototypeTest()
    {
    ReportManager *rm = ReportManager::getInstance();

    WeeklyReport *wr =new WeeklyReport ("design model", 1, "tly", "design domain object model","no accessory");
    MonthlyReport *mr =new MonthlyReport("complete design all model", 3, "tly", "complte design all domain object model","month accessory");

    cout << "$$$$$$$$$$origin$$$$$$$$$$" << endl;
    wr->display();
    mr->display();

    rm->addReport("week", wr);
    rm->addReport("month", mr);

    AbstractReport *wrCopy = rm->getReport("week");
    AbstractReport *mrCopy = rm->getReport("month");

    cout << "$$$$$$$$$$copy$$$$$$$$$$" << endl;
    wrCopy->setReportTitle("change weekly report title"); //修改title
    wrCopy->display();
    mrCopy->display();

    delete wr; wr = NULL;
    delete mr; mr = NULL;
    delete wrCopy; wrCopy = NULL;
    delete mrCopy; mrCopy = NULL;
    }

运行结果:

$$$$$$$$$$origin$$$$$$$$$$
weekly report title: design model
weekly no: 1
reporter: tly
description: design domain object model
accessory: no accessory
monthly report title: complete design all model
monthly no: 3
reporter: tly
description: complte design all domain object model
accessory: month accessory
$$$$$$$$$$copy$$$$$$$$$$
weekly report title: change weekly report title
weekly no: 1
reporter: tly
description: design domain object model
accessory: no accessory
monthly report title: complete design all model
monthly no: 3
reporter: tly
description: complte design all domain object model
accessory: month accessory