声明:转载请附上原文链接

提示:标题序号从3开始,是照应不同设计模式笔记发布的顺序而定的,比如,第上一篇文章 初学Java常用设计模式之——工厂模式 序号从2开始。

标题后面之所以加上了解,是因为相对于单例模式,工厂模式来说原型模式用的比较少,但原型模式的深拷贝和浅拷贝是需要了解一下的!

3. 原型模式(了解)

3.1 原型模式介绍

  • 原型设计模式Prototype

    • 是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能
    • 工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程
    • 应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式
  • 核心组成
    • Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
    • ConcretePrototype : 具体原型类
    • Client: 让一个原型对象克隆自身从而创建一个新的对象
  • 应用场景
    • 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
    • 如果系统要保存对象的状态,做备份使用

3.2 原型模式案例

首先我们来创建一个具体原型类 Person.java 并让其实现 Cloneable 接口,重写clone() 方法:

/*** @Auther: csp1999* @Date: 2020/11/08/7:31* @Description: Person 具体原型类实现 Cloneable 接口,能被克隆*/
public class Person implements Cloneable{private String name;private int age;public Person(){System.out.println("空参构造函数调用...");}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}/*** 重写克隆方法,返回Person对象类型** 注意:权限改成public,方便调用* @return* @throws CloneNotSupportedException*/@Overridepublic Person clone() throws CloneNotSupportedException {return (Person) super.clone();}
}

在测试类中调用并打印结果:

@Test
public void testPropotype() throws CloneNotSupportedException {Person person1 = new Person();person1.setAge(22);person1.setName("csp");System.out.println(person1);Person person2 = person1.clone();person2.setName("hzw");System.out.println(person2);
}

结果如下:

空参构造函数调用...
Person{name='csp', age=22}
Person{name='hzw', age=22}

从结果可以看出:person2 是 person1通过复制后得来的,二者数据内容相同。但需要注意的是,person1调用clone();方法得到person2,并没有经过Person 类中的空参构造函数,因此打印结果只输出一次空参构造函数调用...

接下来,我们在Person 在加上新的复杂数据类型的成员变量:List

private List<String> list;

再来测试:

@Test
public void testPropotype() throws CloneNotSupportedException {Person person1 = new Person();person1.setAge(22);person1.setName("csp");// 初始化list 并为其加入数据person1.setList(new ArrayList<>());person1.getList().add("aaa");person1.getList().add("bbb");System.out.println("person1:"+person1);Person person2 = person1.clone();person2.setName("hzw");// 给peron2 中的list添加一条数据person2.getList().add("ccc");System.out.println("person2"+person2);System.out.println("person1:"+person1);boolean flag1 = person1 == person2;System.out.println("person1 和 person2 的 引用地址是否相同: " +  flag1);boolean flag2 = person1.getList() == person2.getList();System.out.println("person1 和 person2 的 list 引用地址是否相同: " +  flag2);
}

输出结果:

空参构造函数调用...
person1:Person{name='csp', age=22, list=[aaa, bbb]}
person2Person{name='hzw', age=22, list=[aaa, bbb, ccc]}
person1:Person{name='csp', age=22, list=[aaa, bbb, ccc]}
person1 和 person2 的 引用地址是否相同: false
person1 和 person2 的 list 引用地址是否相同: true

由结果可以看出:

  • 当克隆执行完成后,实际上相当于新 new 一个Person 对象并为其分配了新的存储地址及引用,因此person1 和 person2 的地址引用不同;
  • 而对于Person 对象的复杂类型成员变量 list,当执行克隆的时候,实际上是从被拷贝对象person1 中 拷贝了list 的引用地址给person2 中的 list,而并非新new(创建)一个 List 出来;
  • 因此二者其实是共享一个相同地址引用的list,所以person1.getList() == person2.getList();true,这也就说明了,为什么在 person2的list 中添加数据ccc时,person1 中的list也添加了ccc,而这种情况就被称为 浅拷贝;

那么如何解决浅拷贝的问题呢?请接着往下阅读!

3.1 原型模式深拷贝/浅拷贝

  • 遗留问题:

    • 通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的

    • 浅拷贝实现 Cloneable,深拷贝是通过实现 Serializable 读取二进制流

    • 拓展

      • 浅拷贝:

        如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象;
        如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,
        也就是说原型对象和克隆对象的成员变量指向相同的内存地址通过覆盖Object类的clone()方法可以实现浅克隆
        
      • 深拷贝:

        无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等方式来实现
        
  • 优点

    • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率
    • 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态
  • 缺点

    • 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
    • 在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引用时,需要对每一层对象对应的类都必须支持深克隆

深拷贝实现:

首先Person 对象实现 Serializable 接口,然后自定义深拷贝方法 deepClone()

/*** 深拷贝* * 注意:要实现序列化接口* @return*/
public Person deepClone() {try {// 输出 (序列化)ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(this);// 输入 (反序列化)ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);Person person = (Person) ois.readObject();return person;} catch (Exception e) {e.printStackTrace();return null;}
}

接下来验证一下深拷贝是否成功:

@Test
public void testPropotype() throws CloneNotSupportedException {Person person1 = new Person();person1.setAge(22);person1.setName("csp");// 初始化list 并为其加入数据person1.setList(new ArrayList<>());person1.getList().add("aaa");person1.getList().add("bbb");System.out.println("person1:"+person1);//-----------------------------浅拷贝-------------------------------//Person person2 = person1.clone();//-----------------------------深拷贝-------------------------------Person person2 = person1.deepClone();person2.setName("hzw");// 给peron2 中的list添加一条数据person2.getList().add("ccc");System.out.println("person2"+person2);System.out.println("person1:"+person1);boolean flag1 = person1 == person2;System.out.println("person1 和 person2 的 引用地址是否相同: " +  flag1);boolean flag2 = person1.getList() == person2.getList();System.out.println("person1 和 person2 的 list 引用地址是否相同: " +  flag2);
}

输出结果:

空参构造函数调用...
person1:Person{name='csp', age=22, list=[aaa, bbb]}
person2Person{name='hzw', age=22, list=[aaa, bbb, ccc]}
person1:Person{name='csp', age=22, list=[aaa, bbb]}
person1 和 person2 的 引用地址是否相同: false
person1 和 person2 的 list 引用地址是否相同: false

由结果可得出:深拷贝 person2 所得到的 list 内存地址和原来person1 中的内存地址是不同的,深拷贝成功!

之后我会陆续更新其他设计模式博文,如果文章对您有帮助,希望点个赞/收藏/关注! O(∩_∩)O~

初学Java常用设计模式之——原型模式相关推荐

  1. 初学Java常用设计模式之——工厂模式

    声明:转载请附上原文链接 提示:标题序号从2开始,是照应不同设计模式笔记发布的顺序而定的,比如,第上一篇文章 初学Java常用设计模式之--单例模式 序号从1开始 2. 工厂模式(常用) ⼯⼚模式介绍 ...

  2. 初学Java常用设计模式之——装饰器模式

    声明:转载请附上原文链接 提示:标题序号从8开始,是照应不同设计模式笔记发布的顺序而定的,比如,上一篇文章 初学Java常用设计模式之--桥接模式和组合模式 序号从7开始. 8. 装饰器设计模式(重点 ...

  3. Java设计模式05:常用设计模式之原型模式(创建型模式)

    1. Java之原型模式(Prototype Pattern)     原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象. ...

  4. 初学Java常用设计模式之——单例模式

    0. 常⻅的三⼤设计模式分类 创建型模式:提供了⼀种在创建对象的同时隐藏创建逻辑的⽅式,使 得程序在判断针对某个给定实例需要创建哪些对象时更 加灵活,比如: 常用4个:⼯⼚模式.抽象⼯⼚模式.单例模式 ...

  5. Java常用设计模式————享元模式

    引言 享元模式,也叫蝇量模式(Flyweight Pattern).运用共享技术有效地支持大量细粒度的对象. 享元模式常用于系统底层开发,解决系统的性能问题.例如数据库连接池,里面都是创建好的连接对象 ...

  6. Java常用设计模式————装饰者模式

    引言 装饰者模式,又叫装饰器模式.它可以动态的将新功能附加到对象上.在对象功能扩展方面,它比继承更灵活,同时装饰者模式也体现了OCP原则. 在客户端调用使用了装饰者模式的对象时,就好像在使用构造器层层 ...

  7. Java常用设计模式————抽象工厂模式

    简介 每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例. 与工厂方法模式的区别 工厂方法模式针对的是一个产品等级结构,而抽象工厂模式针对的是多个产品等级结构,因此抽象工厂模式在结构上要比工厂方 ...

  8. Java常用设计模式————原型模式(一)

    介绍 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 原型模式用于创建重复的对象,同时又能保证性能.当直接创建对象的代价比较大时,则采用 ...

  9. java prototype是什么,java设计模式-原型模式(Prototype)

    定义 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是原型模式的用意 原型模式的结构 原型模式要求对象实现同一个可 ...

最新文章

  1. Kotlin 中 Activity 跳转问题
  2. 目标检测:Anchor-Free时代
  3. 高房价可控制人口增长与人口素质?
  4. boost::describe模块实现string转enum的测试程序
  5. 哪一类人用苹果手机最多?
  6. matlab boundaries和fchcode函数无法执行的解决办法 未定义与 'double' 类型的输入参数相对应的函数 'boundaries'
  7. poj 1325 Machine Schedule 匈牙利二分匹配 基础
  8. Python中logging日志使用
  9. php music player,cloudmusic.php
  10. 尔雅 科学通史(吴国盛) 个人笔记及课后习题 2018 第四章 中国独立发展的科技文明
  11. python mysqldb_python MySQLdb API手册
  12. python求逆矩阵的方法,Python 如何求矩阵的逆
  13. ECCV2022细粒度图像检索SEMICON学习记录
  14. 教你彻底卸载MySQL 并重装(保姆级教程 )
  15. 为什么你比同龄人更显老?答案可能和“久坐”有关
  16. PM 如何进行测试?
  17. maplesim matlab,MapleSim 2020
  18. XStream java.lang.ClassNotFoundException 问题解决
  19. BeautifulSoup详解
  20. 【android精品源码系列】安卓线上购物商城

热门文章

  1. 图像的阈值分割(Optimum Thresholding)
  2. 三款ActiveX图表控件对比评测 Pro ActiveX、ProEssentials、ChartDirector
  3. 2022中级Android开发面试解答,当上项目经理才知道
  4. 如何成为一个更好的Android开发者?写给正在求职的安卓开发
  5. C#丨DataGridView控件获取选中行的某一列的值
  6. 程序员群嘲红芯浏览器:注释过度很业余 创新混淆视听
  7. matlab中axis函数程序,matlab中axis函数
  8. 免费mysql客户端推荐
  9. java通过输入的方式给int数组赋值并输出数组
  10. linux-查看cpu核数