克隆以后我就成了你——探究原型模式

2022-07-24 16:40:03 浏览数 (1)

清晰地记得中学生物课本上提到过的克隆羊“多利”,虽然多利寿命不长,但它的出现对“克隆(Clone)”技术意义重大。克隆,直观说就是从原有生物体上取体细胞,然后无性繁殖出有完全相同基因的个体或种群。而本文将要介绍的原型模式,将克隆技术应用到了软件设计层面。

1

原型模式简介

原型模式通过复制一个已有对象来获取更多相同或者相似的对象。原型模式定义如下:

使用原型实例指定待创建对象的类型,并且通过复制这个原型阿里创建型的对象。

原型模式的工作原理是将一个原型对象传给要发动穿件的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程。从工厂方法角度而言,创建新对象的工厂就是原型类自己。软件系统中有些对象的创建过程比较复杂,且有时需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。

2

原型模式结构

原型式的结构包含以下几个角色:

  • 抽象原型类(AbstractPrototype):声明克隆clone自身的接口
  • 具体原型类(ConcretePrototype):实现clone接口
  • 客户端(Client):客户端中声明一个抽象原型类,根据客户需求clone具体原型类对象实例

原型模式的UML图如下:

3

深拷贝与浅拷贝

原型模式可以说是“复制”,即克隆,但这个复制不是代码的复制,而是将对象包含的所有属性都创建一份拷贝。但不同的复制操作,可能会产生两种不同的拷贝,即浅拷贝和深拷贝。

浅拷贝

在浅拷贝中,如果原型对象的成员变量是值类型(如int、double、char等基本数据类型),将复制一份给拷贝对象;如果原型对象的成员变量是引用/指针,则将引用/指针指向的地址拷贝一份给拷贝对象,即原型对象和拷贝对象中的成员变量指向同一个地址

深拷贝

在深拷贝中,无论原型对象中的成员变量是值类型还是指针/引用类型,都将复制一份给拷贝对象。注意,深拷贝中,指针/引用对象也会被拷贝一份给拷贝对象。

下图举例说明了浅拷贝与深拷贝的区别:

4

代码实例

读书时每个周一Jungle都会陷入苦恼,因为作业还没完成。于是Jungle想拿着老哥Single的作业来抄一份。虽然抄袭作业并不好,但是边抄边学借鉴一下也是可以的。于是乎……

作业包含几个部分:姓名(name)、学号(idNum)、模型(workModel)。首先定义一个workModel类:

代码语言:javascript复制
//work model类
class WorkModel
{
public:
  char *modelName;
  void setWorkModelName(char *iName){
    this->modelName = iName;
  }
};

该实例UML图如下:

定义原型类和克隆方法

代码语言:javascript复制
//抽象原型类PrototypeWork
class PrototypeWork
{
public:
  PrototypeWork(){}
  virtual PrototypeWork *clone() = 0;
 
private:
  
};
 
//具体原型类ConcreteWork
class ConcreteWork :public PrototypeWork
{
public:
  ConcreteWork(){}
  ConcreteWork(char* iName, int iIdNum, char* modelName){
    this->name = iName;
    this->idNum = iIdNum;
    this->workModel = new WorkModel();
    this->workModel->setWorkModelName(modelName);
  }
  
  ConcreteWork *clone(){
    ConcreteWork *work = new ConcreteWork();
    work->setName(this->name);
    work->setIdNum(this->idNum);
    work->workModel = this->workModel;
    return work;
  }
 
  void setName(char* iName){
    this->name = iName;
  }
  void setIdNum(int iIdNum){
    this->idNum = iIdNum;
  }
  void setModel(WorkModel *iWorkModel){
    this->workModel = iWorkModel;
  }
  //打印work信息
  void printWorkInfo(){
    printf("name:%stn", this->name);
    printf("idNum:%dtn", this->idNum);
    printf("modelName:%stn", this->workModel->modelName);
  }
private:
  char* name;
  int idNum;
  WorkModel *workModel;
};

客户端使用代码示例

4.1.示例一:浅拷贝

代码语言:javascript复制
#include "PrototypePattern.h"
 
int main()
{
  ConcreteWork *singleWork = new ConcreteWork("Single",1001,"Single_Model");
  printf("nSingle的作业:n");
  singleWork->printWorkInfo();
  
  printf("njungle直接抄作业……n");
  ConcreteWork *jungleWork = singleWork;
  printf("nJungle的作业:n");
  jungleWork->printWorkInfo();
 
  //抄完改名字和学号,否则会被老师查出来
  printf("njungle抄完改名字和学号,否则会被老师查出来……n");
  jungleWork->setName("jungle");
  jungleWork->setIdNum(1002);
  WorkModel *jungleModel = new WorkModel();
  jungleModel->setWorkModelName("Jungle_Model");
  jungleWork->setModel(jungleModel);
  
  //检查下是否改对了
  printf("nSingle的作业:n");
  singleWork->printWorkInfo();
  printf("nJungle的作业:n");
  jungleWork->printWorkInfo();
 
  system("pause");
  return 0;
}

效果如下图:

显然,这不是我们想要的结果。接下来我们使用clone方法。

4.2.示例二:深拷贝

代码语言:javascript复制
#include "PrototypePattern.h"
 
int main()
{
  ConcreteWork *singleWork = new ConcreteWork("Single", 1001, "Single_Model");
  printf("nSingle的作业:n");
 
  ConcreteWork *jungleWork = singleWork->clone();
  printf("nJungle的作业:n");
 
  //抄完改名字和学号,否则会被老师查出来
  printf("njungle抄完改名字和学号,否则会被老师查出来……n");
  jungleWork->setName("jungle");
  jungleWork->setIdNum(1002);
  WorkModel *jungleModel = new WorkModel();
  jungleModel->setWorkModelName("Jungle_Model");
  jungleWork->setModel(jungleModel);
 
  //检查下是否改对了
  printf("nSingle的作业:n");
  singleWork->printWorkInfo();
  printf("nJungle的作业:n");
  jungleWork->printWorkInfo();
 
  system("pause");
  return 0;
}

效果如下图:

这才是一次完美的抄作业!

5

总结

优点:

  • 当创建新的对象实例较为复杂时,原型模式可以简化创建过程,提高创建对象的效率;
  • 可扩展:模式中提供了抽象原型类,具体原型类可适当扩展;
  • 创建结构简单:创建工厂即为原型对象本身

缺点:

  • 深克隆代码较为复杂;
  • 每一个类都得配备一个clone方法,且该方法位于类的内部,修改时违背开闭原则;

适用环境:

  • 当创建新的对象实例较为复杂时,原型模式可以简化创建过程;
  • 结合优点第3条,需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少几个的组合状态,通过复制原型对象得到新实例,比通过使用构造函数创建一个新实例会更加方便。

·END·

0 人点赞