一、简介

  • 概念: 使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象,原型模式属于创建型模式,它提供了一种创建对象的最佳方式。

形象比喻:细胞复制分裂,下面是网上找的细胞分裂图

由图可见,复制新细胞的行为也是细胞自身发起,其实就是说原型对象自己不仅是个对象还是个工厂。

原型模式,通过克隆方式创建的对象是全新的对象,它们都是有自己的新的地址,hashcode不同, 复制新生成的对象在修改后,不会影响到之前被复制的对象,每一个克隆对象都是相对独立的。

二、角色说明

原型模式中的角色主要有:抽象原型类、具体原型类、客户端使用类三种角色。下面是各个角色的说明:

  • 抽象原型类(prototype):声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。抽象原型类需要具备以下两个条件:
  1. 实现Cloneable接口,说明可以使用clone方法。只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  2. 重写Object类中的clone方法,clone方法从Object基类继承下来,作用是返回对象的一个拷贝;
  • 具体原型类(concretePrototype):实现了抽象原型类中声明的克隆方法,返回一个自己的克隆对象;
  • 客户端类(Client):使用原型对象只需要通过工厂方式创建或者直接new(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象;

三、示例

下面通过一个简单的示例说明原型模式的应用,UML类图如下:

相关代码:

【a】抽象原型类

package com.wsh.springboot.springbootdesignpattern.prototypepattern.prototype;/*** @Description: 抽象原型类(prototype)* @author: weishihuai* @Date: 2019/10/22 17:57*/
public abstract class AbstractShape implements Cloneable {/*** 绘制图形方法*/abstract void draw();/*** 名称*/private String name;public void setName(String name) {this.name = name;}@Overrideprotected Object clone() {Object object = null;try {object = super.clone();} catch (CloneNotSupportedException e) {System.out.println(e.getMessage());e.printStackTrace();}return object;}@Overridepublic String toString() {return "AbstractShape{" +"name='" + name + '\'' +'}';}
}

可见,抽象原型类实现了Cloneable接口并且重写了Object.clone()方法,返回了一个拷贝之后的对象。

【b】具体原型类

/*** @Description: 长方形具体原型类(concretePrototype)* @author: weishihuai* @Date: 2019/10/22 17:58*/
public class Rectangle extends AbstractShape {public Rectangle() {setName("长方形");}@Overridevoid draw() {System.out.println("绘制长方形...");}@Overridepublic String toString() {return super.toString();}
}/*** @Description: 圆形具体原型类(concretePrototype)* @author: weishihuai* @Date: 2019/10/22 17:58*/
public class Circle extends AbstractShape {public Circle() {setName("圆形");}@Overridevoid draw() {System.out.println("绘制圆形...");}@Overridepublic String toString() {return super.toString();}
}

【c】客户类

/*** @Description: 客户类(Client)* @author: weishihuai* @Date: 2019/10/22 18:01*/
public class Client {public static void main(String[] args) {AbstractShape rectangle = new Rectangle();System.out.println(rectangle + ">>>>>>" + rectangle.hashCode());Rectangle cloneRectangle = (Rectangle) rectangle.clone();System.out.println(cloneRectangle + ">>>>>>" + cloneRectangle.hashCode());Rectangle cloneRectangle2 = (Rectangle) rectangle.clone();System.out.println(cloneRectangle2 + ">>>>>>" + cloneRectangle2.hashCode());}
}

【d】运行结果

可见,拷贝之后的对象与原对象hashCode不一样,他们是完全不同的两个对象。

由原型模式引出的两个概念:浅克隆和深克隆。

  • 浅克隆:在复制对象的时候,只会复制简单基本数据类型和String等,对于引用类型的话,只会复制引用,就是它和原对象指向的还是同一个地址,修改之后会影响到旧对象对应的值。
  • 深克隆:就是在复制对象的时候,无论是基本数据类型,还是引用类型,如数组、对象等,全部都会重复复制多一份,复制之后的新对象和旧对象相互独立,互不影响,当修改克隆对象后对于原型对象没有丝毫影响。

综上,在实际工作中,我们肯定要实现深克隆,浅克隆存在一些问题,在复制基本数据类型时还是可以用的。

四、浅克隆

下面通过一个简单的示例说明浅克隆:

【a】教师类

/*** @Description: 教师类* @author: weishihuai* @Date: 2019/10/22 20:10*/
public class Teacher {private Integer pkid;private String name;public Teacher(Integer pkid, String name) {this.pkid = pkid;this.name = name;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Teacher{" +"pkid='" + pkid + '\'' +", name='" + name + '\'' +'}';}
}

【】【b】学生类

package com.wsh.springboot.springbootdesignpattern.prototypepattern.shallowclone;/*** @Description:* @author: weishihuai* @Date: 2019/10/22 20:11*/
public class Student implements Cloneable {private double weight;private Integer pkid;private String name;private Teacher teacher;public Student(double weight, Integer pkid, String name, Teacher teacher) {this.weight = weight;this.pkid = pkid;this.name = name;this.teacher = teacher;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}@Overrideprotected Object clone() {Object object = null;try {object = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();System.out.println(e.getMessage());}return object;}@Overridepublic String toString() {return "Student{" +"weight=" + weight +", pkid=" + pkid +", name='" + name + '\'' +", teacher=" + teacher +'}';}
}

【c】测试用例

/*** @Description: 客户端* @author: weishihuai* @Date: 2019/10/22 20:13*/
public class Client {public static void main(String[] args) {Teacher teacher = new Teacher(1, "黄老师");Student student = new Student(50.0D, 1, "张三", teacher);Student student1 = (Student) student.clone();System.out.println(student == student1);System.out.println(student.getTeacher() == student1.getTeacher());System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());System.out.println("克隆之前student学生的老师: " + student.getTeacher().getName());//克隆对象student1将老师姓名修改为张老师,导致旧student的老师姓名也被修改。Teacher teacher1 = student1.getTeacher();teacher1.setName("张老师");System.out.println("克隆之后student学生的老师: " + student.getTeacher().getName());System.out.println("克隆之后的新学生的老师: " + student.getTeacher().getName());}
}

【d】运行结果

可见浅拷贝之后的student对象与原来的对象是不同的对象,但是student对象里面的teacher对象与原teacher还是指向的同一个地址,旧对象修改会导致新对象也被修改,这就是浅拷贝的缺点,浅拷贝在克隆基本数据类型的时候比较常见。

注意:String是一个例外,但对于我们编程来说可以和操作基本数据类型一样做,基本没影响,大大方便了我们的编程。String类型的变量clone后的表现好象也实现了深度clone,但其实只是一个假象。因为String是一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。

五、深克隆

还是用上面浅克隆的例子说明怎么实现深克隆,深克隆主要用两种实现方法: 重写clone()方法、序列化方式

(一)、重写clone()方法

【a】学生类

public class Student implements Cloneable {private double weight;private Integer pkid;private String name;private Teacher teacher;public Student(double weight, Integer pkid, String name, Teacher teacher) {this.weight = weight;this.pkid = pkid;this.name = name;this.teacher = teacher;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}@Overrideprotected Object clone() {Student student = null;try {student = (Student) super.clone();student.teacher = (Teacher) this.teacher.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();System.out.println(e.getMessage());}return student;}@Overridepublic String toString() {return "Student{" +"weight=" + weight +", pkid=" + pkid +", name='" + name + '\'' +", teacher=" + teacher +'}';}
}

【b】教师类

/*** @Description: 教师类* @author: weishihuai* @Date: 2019/10/22 20:10*/
public class Teacher implements Cloneable {private Integer pkid;private String name;public Teacher(Integer pkid, String name) {this.pkid = pkid;this.name = name;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Teacher{" +"pkid='" + pkid + '\'' +", name='" + name + '\'' +'}';}@Overrideprotected Object clone() {Object object = null;try {object = super.clone();} catch (CloneNotSupportedException e) {System.out.println(e.getMessage());e.printStackTrace();}return object;}
}

【c】 客户端

/*** @Description: 客户端* @author: weishihuai* @Date: 2019/10/22 20:13*/
public class Client {public static void main(String[] args) {Teacher teacher = new Teacher(1, "黄老师");Student student = new Student(50.0D, 1, "张三", teacher);Student student1 = (Student) student.clone();System.out.println(student);System.out.println(student1);System.out.println(student == student1);System.out.println(student.getTeacher() == student1.getTeacher());System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());}
}

【d】运行结果

可见,复制之后的student对象以及对应的teacher对象都是全新的,与旧对象互不影响,这就是深克隆。深拷贝关键点在于,实现cloneable接口以及用object的clone方法。

(二)、序列化方式

简单的讲就是序列化就将对象写到流的一个过程,写到流里面去(就是字节流)就等于复制了对象,但是原来的对象并没有动,只是复制将类型通过流的方式进行读取,然后写到另一块内存地址中。

【a】教师类

import java.io.Serializable;/*** @Description: 教师类* @author: weishihuai* @Date: 2019/10/22 20:10*/
public class Teacher implements Serializable {private Integer pkid;private String name;public Teacher(Integer pkid, String name) {this.pkid = pkid;this.name = name;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Teacher{" +"pkid='" + pkid + '\'' +", name='" + name + '\'' +'}';}}

【b】学生类

import java.io.*;/*** @Description:* @author: weishihuai* @Date: 2019/10/22 20:11*/
public class Student implements Serializable {private double weight;private Integer pkid;private String name;private Teacher teacher;public Student(double weight, Integer pkid, String name, Teacher teacher) {this.weight = weight;this.pkid = pkid;this.name = name;this.teacher = teacher;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}public Integer getPkid() {return pkid;}public void setPkid(Integer pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}@Overridepublic String toString() {return "Student{" +"weight=" + weight +", pkid=" + pkid +", name='" + name + '\'' +", teacher=" + teacher +'}';}public Student deepCloneBySerializable() {Student student = null;ByteArrayOutputStream byteArrayOutputStream = null;ObjectOutputStream objectOutputStream = null;ByteArrayInputStream byteArrayInputStream = null;ObjectInputStream objectInputStream = null;try {byteArrayOutputStream = new ByteArrayOutputStream();objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(this);byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());objectInputStream = new ObjectInputStream(byteArrayInputStream);student = (Student) objectInputStream.readObject();return student;} catch (IOException | ClassNotFoundException e) {e.printStackTrace();} finally {if (null != objectInputStream) {try {objectInputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != byteArrayInputStream) {try {byteArrayInputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != objectOutputStream) {try {objectOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != byteArrayOutputStream) {try {byteArrayOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}return student;}
}

【c】客户端

/*** @Description: 客户端* @author: weishihuai* @Date: 2019/10/22 20:13*/
public class Client {public static void main(String[] args) {Teacher teacher = new Teacher(1, "黄老师");Student student = new Student(50.0D, 1, "张三", teacher);Student student1 = (Student) student.deepCloneBySerializable();System.out.println(student);System.out.println(student1);System.out.println(student == student1);System.out.println(student.getTeacher() == student1.getTeacher());System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());}
}

这就是使用序列化方式深拷贝对象,推荐使用此种方式实现深拷贝,避免重写clone()方法逻辑太多于复杂。

六、总结

  • 优点:
  1. 使用原型模式创建对象比直接new一个对象在性能上要好的多,性能提高;
  2. 逃避构造函数的约束,使用原型模式复制对象不会调用类的构造方法,直接在内存中复制数据;
  3. 简化对象的创建;
  • 缺点:
  1. 原型模式需要每个类都实现Cloneable接口,并且各自重写clone()克隆方法,这些对于新创建的类影响不大,但是对于已存在的类的话,需要一个一个去修改源代码,这违背了开闭原则。
  2. 在实现深克隆时需要编写较为复杂的代码。
  • 适用场景:
  • 1. 当直接创建对象的代价比较大时,占用CPU大时;例如,一个对象需要在一个高代价的数据库操作之后被创建,我们可以先将对象缓存起来,下次请求的时候直接返回缓存对象,如果需要修改,再对其稍作修改;
  • 2. 一个对象多个修改者的场景;
  • 3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用;
  • 4. 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者;

设计模式 (五) 原型模式相关推荐

  1. 设计模式五: 原型模式(Prototype)

    简介 原型模式是属于创建型模式的一种,是通过拷贝原型对象来创建新的对象. 万能的Java超类Object提供了clone()方法来实现对象的拷贝. 可以在以下场景中使用原型模式: 构造函数创建对象成本 ...

  2. C#设计模式(6)——原型模式(Prototype Pattern)

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  3. 设计模式回顾——原型模式(C++)

    文章目录 1 前言 2 什么是原型模式 2.1 原型模式组成 2.2 原型模式UML图 2.3 原型模式作用 3 原型模式优缺点 4 什么地方使用原型模式 5 原型模式实现 6 原型模式与构造函数 1 ...

  4. 【GOF23设计模式】原型模式

    [GOF23设计模式]原型模式 来源:http://www.bjsxt.com/  一.[GOF23设计模式]_原型模式.prototype.浅复制.深复制.Cloneable接口  浅复制 1 pa ...

  5. 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)

    [索引页] [源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:webabcd 介绍 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象. ...

  6. 教你如何一篇博客读懂设计模式之—--原型模式

    教你如何一篇博客读懂设计模式之----原型模式 what:是什么 原型模式: 用于创建重复的对象,既不用一个属性一个属性去set和get,又不影响性能,原型模式产生的对象和原有的对象不是同一个实例,他 ...

  7. 二十三种设计模式之原型模式

    今天继续探讨GOF二十三种设计模式的原型模式,原型模式也是属于创建型模式的一种 原型模式通俗的讲就是对象复制的过程,即通过一个原型对象,我可以得到一个该对象的克隆. 下面来看下原型模式的第一种写法-- ...

  8. 「设计模式(五) - 代理模式」

    「设计模式(五) - 代理模式」 一.处处可见的"代理" "代理"在平常生活司空见惯,点外卖,租房子找中介,买飞机票等等.基本上用手机就能完成,也就是不直接接触 ...

  9. 设计模式之 原型模式

    原型模式应用场景举例:  GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让MM有些不高心,MM说回复信息这 ...

  10. Head First设计模式之原型模式

    一.定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式.在实际应用中,原型模式很少单独出现 ...

最新文章

  1. 前端学Markdown
  2. C++二维数组new小结(zz)
  3. yuv420,yuv420p,yuv420sp,nv12,nv21,I420,YV12概念区别
  4. 浏览器工作原理(四):浏览器事件解读
  5. Android studio导入第三方库的各种方法和eclipse的导入方式对比
  6. QEMU 中音频模拟如何工作
  7. MySQL(五)MySQL事务
  8. P4849 寻找宝藏(模板:四维偏序)
  9. 工业RS485接口电路设计
  10. UVa-679 Dropping Balls(二叉树的编号)
  11. 注意,更改团队所属业务部门用Update消息无效!
  12. Docker之Jitsi Meet视频会议服务
  13. 原生javascript分页 html分页与ajax数据请求结合使用 前端js分页
  14. Python爬虫+简易词云的制作
  15. 推荐电影:亚当桑德勒的《我盛大的同志婚礼》
  16. Python:利用cv2模块识别手势
  17. fluent在运行时改变重力方向方法总结
  18. lm80认证_你们做过LM-80测试什么产品需要做LM-80测试
  19. elasticsearch安装和配置,elasticsearch启动报错:can not run elasticsearch as root
  20. 中文句法分析及LTP使用

热门文章

  1. 信贷违约风险预测(三)简单的特征工程
  2. c语言显示cpuid_ccpuid:CPUID信息模块。范例:显示所有的CPUID信息
  3. 用html设计倒计时秒表,Javascript实现秒表倒计时功能
  4. 382.链表随机节点
  5. python 如何做web界面_python的web实例教程,用python做个简单web界面选什么框架最简单...
  6. 主题图标_【主题爱好者】简约全局主题【归途】
  7. 以代码为实例讲解sizeof函数易混淆的用法
  8. 【DL小结1】DL入门
  9. 最新android工程目录下armeabi-v7a,armeabi的具体含义,有什么区别
  10. latex在行末出现百分号的作用