设计模式之原型模式

2018-09-18 22:19:54

 原型模式(Prototype)

  原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象再创建另一个对象,而且不需要知道任何创建的细节。

原型模式的UML类图

其中:Prototype是一个抽象原型了(虚基类),声明了一个克隆自己的方法

           ConcretePrototype1是具体原型类,声明了一个克隆自己的方法

           Clinet代码中则需要使用具体原型类,让它克隆自己,从而创建一个新的对象。

原型模式的优缺点

优点:

  利用原型模式来创建新对象时,我们只需要创建new出一个原型,然后用这个原型去初始化其它新的对象。这样带来的好处是,如果new一个新对象的成本比较大,那么使用原型模式有助于节省开支。另外可以在程序运行时获得一份相同的实例,它们之间不会相互干扰。

缺点:

  会遇到深拷贝浅拷贝的问题,在实现深拷贝时实现会比较麻烦。需要每个类配备一个克隆方法,而且该克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。

适用场景: 

  • 如果创建新对象成本较大,可以利用已有的对象进行复制来获得。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。

深拷贝与浅拷贝

  深浅拷贝的这段描述引用自:http://www.cnblogs.com/BlueTzar/articles/1223313.html

  对于普通成员变量来说,复制是一件很容易的事情,直接使用赋值运算即可达到目标。但是对于类对象来说,拷贝相对比较复杂,因为一般而言类对象内部结构较为复杂,因为存在着各种成员变量。当含有指针成员的时候这种拷贝将会变得更加复杂。

  对类对象而言,相同类型的对象之间的复制是通过拷贝构造函数来实现整个复制过程的,例如:

 Copy Construct

  拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(const X& x)。

  当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
  1.一个对象以值传递的方式传入函数体 
  2.一个对象以值传递的方式从函数返回 
  3.一个对象需要通过另外一个对象进行初始化。

  如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝。自己编写拷贝构造函数防止编译器自动生成的拷贝构造函数是一个比较好的做法,可以提高程序效率。

浅拷贝和深拷贝

  在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。一个深拷贝的小例子:

#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b,char* cstr)
  {
   a=b;
   str=new char[b];
   strcpy(str,cstr);
  }
  CA(const CA& C)
  {
   a=C.a;
   str=new char[a]; //深拷贝
   if(str!=0)
    strcpy(str,C.str);
  }
  void Show()
  {
   cout<<str<<endl;
  }
  ~CA()
  {
   delete str;
  }
 private:
  int a;
  char *str;
};

int main()
{
 CA A(10,"Hello!");
 CA B=A;
 B.Show();
 return 0;
} 
深拷贝

代码示例

1.抽象原型(这里面最关键的部分就是这个clone成员函数)

#ifndef PROTOTYPE_H_
#define PROTOTYPE_H_
#include <string>
class Prototype
{
public:
    virtual void setName(const std::string strName) = 0;
    virtual void setPersonInfo(const std::string strSex,const int iAge) = 0;
    virtual void setWorkExperience(const std::string strCompanyName,const std::string strTimeArea) = 0;
    virtual Prototype* clone() = 0;
    virtual const int getAge() const = 0;
    virtual const std::string& getSex() = 0;
    virtual const std::string& getName() = 0;
    virtual const std::string& getCompanyName() = 0;
    virtual const std::string& getTimeArea() = 0;
    Prototype() = default;
    virtual ~Prototype() = default;
protected:
    std::string m_strName;
    int m_iAge;
    std::string m_strSex;
    std::string m_strCompany;
    std::string m_strTimeArea;
};
#endif 
Prototype

2.具体原型类(clone负责拷贝它自己)

#ifndef CONCRETEPROTOTYPE1_H_
#define CONCRETEPROTOTYPE1_H_
#include "Prototype.h"
class ConcretePrototype1:public Prototype
{
public:
    void setName(const std::string strName);
    void setPersonInfo(const std::string strSex,const int iAge);
    void setWorkExperience(const std::string strCompanyName,const std::string strTimeArea);
    Prototype* clone();
    const int getAge() const;
    const std::string& getSex();
    const std::string& getCompanyName();
    const std::string& getTimeArea();
    const std::string& getName(); 
    ConcretePrototype1() = default;
    ConcretePrototype1(const std::string strName);
    ConcretePrototype1(const ConcretePrototype1& rightOperand);
    ~ConcretePrototype1() = default;
};
#endif

#include "ConcretePrototype1.h"

ConcretePrototype1::ConcretePrototype1(const std::string strName)
{
    m_strName = strName;
}


void ConcretePrototype1::setName(const std::string strName)
{
    m_strName = strName;
}

void ConcretePrototype1::setPersonInfo(const std::string strSex,const int iAge)
{
    m_strSex = strSex;
    m_iAge = iAge;
}

void ConcretePrototype1::setWorkExperience(const std::string strCompanyName,const std::string strTimeArea)
{
    m_strCompany = strCompanyName;
    m_strTimeArea = strTimeArea;
}

const int ConcretePrototype1::getAge() const 
{
    return m_iAge;
}

const std::string& ConcretePrototype1::getSex()
{
    return m_strSex;
}

const std::string& ConcretePrototype1::getCompanyName()
{
    return m_strCompany;
}

const std::string& ConcretePrototype1::getTimeArea()
{
     return m_strTimeArea;
}

const std::string& ConcretePrototype1::getName()
{
    return m_strName;
}

ConcretePrototype1::ConcretePrototype1(const ConcretePrototype1 &rightOperand)
{
    m_strName = rightOperand.m_strName;
    m_iAge = rightOperand.m_iAge;
    m_strSex = rightOperand.m_strSex;
    m_strCompany = rightOperand.m_strCompany;
    m_strTimeArea = rightOperand.m_strTimeArea; 
} 

Prototype* ConcretePrototype1::clone()
{
    return new ConcretePrototype1(*this);
}
ConcreteProtptype1

3.客户端代码

#include "ConcretePrototype1.h"
#include "Prototype.h"
#include <iostream>
using namespace std;
#ifndef SAFEDELETE 
#define SAFEDELETE(p) {if(nullptr != p) { delete (p); (p) = nullptr;}}
#endif

int main(int argc,char *argv[])
{
    ConcretePrototype1 objMyConcretePrototype1("Hu Tao");
    objMyConcretePrototype1.setPersonInfo("Man",18);
    objMyConcretePrototype1.setWorkExperience("Xi Lin","2016-12--2018-9");
    std::cout << "Name is:" << objMyConcretePrototype1.getName() << endl;
    cout << "Sex is:" << objMyConcretePrototype1.getSex() << endl;
    cout << "Age is:" << objMyConcretePrototype1.getAge() << endl;
    cout << "Company's name is:" << objMyConcretePrototype1.getCompanyName() << endl;
    cout << "Time Area is:" << objMyConcretePrototype1.getTimeArea() << endl;
    Prototype* clonePrototype = objMyConcretePrototype1.clone();
    clonePrototype->setName("Yang yang");
    cout << "Name is:" << clonePrototype->getName() << std::endl;
    clonePrototype->setPersonInfo("Woman",22);
    cout << "Sex is:" << clonePrototype->getSex() << endl;
    SAFEDELETE(clonePrototype);
    return(1);
}
Client

Tip:在linux下写代码的时候每一个细节都要注意,没有了VS编译器的自动补全、语法高亮、以及方便的切换源文件,以便查看源文件的实现,多少会带来一些困扰。在类声明时,class{};后的分号一定不要忘记,否则可能会导致很多莫名其妙的问题,这个分号意味着这是一个类型,你可以声明它的实体。另外,纯虚函数的 =0也不要忘了,否则会导致链接的时候产生一些莫名其妙的问题。

原文地址:https://www.cnblogs.com/ToBeExpert/p/9671868.html