• 尚硅谷设计模式(五)原型模式


    由克隆羊问题引出原型模式:

    现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊。

    传统方式

    直接new一个羊的对象,拿过来使用即可,后面向"克隆这只羊",直接把姓名、年龄属性全部复用。

     羊Sheep

    1. public class Sheep {
    2. private String name;
    3. private int age;
    4. private String color;
    5. public Sheep(String name, int age, String color) {
    6. this.name = name;
    7. this.age = age;
    8. this.color = color;
    9. }
    10. //get,set,toString省略
    11. }

    客户端Client 

    1. public class Client {
    2. public static void main(String[] args) {
    3. Sheep sheep = new Sheep("tom", 1, "白");
    4. Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    5. Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    6. Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    7. }
    8. }

    问题分析

    1. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
    2. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

    改进的思路分析

    Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式 

    原型模式

    1、基本介绍

    1)原型模式(Prototype  模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象

    2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节

    3)工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone() 

    2、代码实现

    设计方案

    实现Cloneable接口,重写clone()方法

    羊Sheep

    1. public class Sheep implements Cloneable{
    2. private String name;
    3. private int age;
    4. private String color;
    5. public Sheep(String name, int age, String color) {
    6. this.name = name;
    7. this.age = age;
    8. this.color = color;
    9. }
    10. //省略get set toString方法
    11. //克隆该实例,使用默认的clone方法来完成
    12. @Override
    13. protected Object clone() {
    14. Sheep sheep =null;
    15. try {
    16. sheep= (Sheep) super.clone();
    17. } catch (CloneNotSupportedException e) {
    18. System.out.println(e.getMessage());
    19. }
    20. return sheep;
    21. }
    22. }

     客户端Client

    1. public class Client {
    2. public static void main(String[] args) {
    3. Sheep sheep = new Sheep("tom", 1, "白");
    4. Sheep sheep1 =(Sheep) sheep.clone();
    5. Sheep sheep2 =(Sheep) sheep.clone();
    6. Sheep sheep3 =(Sheep) sheep.clone();
    7. }
    8. }

    浅拷贝

    1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。 

    2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

    3)浅拷贝是使用默认的    clone()方法来实现

    • sheep  =  (Sheep)  super.clone();

    就用刚才克隆羊案例来看,这只羊有个好朋友

    在原来的基础上添加一个Sheep类型的属性;

    1. public class Sheep implements Cloneable{
    2. private String name;
    3. private int age;
    4. private String color;
    5. public Sheep friend;
    6. public Sheep(String name, int age, String color) {
    7. this.name = name;
    8. this.age = age;
    9. this.color = color;
    10. }
    11. //省略get set toString方法
    12. @Override
    13. protected Object clone() {
    14. Sheep sheep =null;
    15. try {
    16. sheep= (Sheep) super.clone();
    17. } catch (CloneNotSupportedException e) {
    18. System.out.println(e.getMessage());
    19. }
    20. return sheep;
    21. }
    22. }

    客户端Client

    1. public class Client {
    2. public static void main(String[] args) {
    3. Sheep sheep = new Sheep("tom", 1, "白");
    4. sheep.friend = new Sheep("jack",2,"蓝");
    5. Sheep sheep1 =(Sheep) sheep.clone();
    6. Sheep sheep2 =(Sheep) sheep.clone();
    7. Sheep sheep3 =(Sheep) sheep.clone();
    8. System.out.println(sheep + "\n--->sheep friend hashCode:" + sheep.friend.hashCode());
    9. System.out.println(sheep1 + "\n--->sheep1 friend hashCode:" + sheep1.friend.hashCode());
    10. System.out.println(sheep2 + "\n--->sheep2 friend hashCode:" + sheep2.friend.hashCode());
    11. System.out.println(sheep3 + "\n--->sheep3 friend hashCode:" + sheep3.friend.hashCode());
    12. }
    13. }

     结果:注意克隆羊的好朋友没有变;

    1. Sheep{name='tom', age=1, color='白', friend=Sheep{name='jack', age=2, color='蓝', friend=null}}
    2. --->sheep friend hashCode:692404036
    3. Sheep{name='tom', age=1, color='白', friend=Sheep{name='jack', age=2, color='蓝', friend=null}}
    4. --->sheep1 friend hashCode:692404036
    5. Sheep{name='tom', age=1, color='白', friend=Sheep{name='jack', age=2, color='蓝', friend=null}}
    6. --->sheep2 friend hashCode:692404036
    7. Sheep{name='tom', age=1, color='白', friend=Sheep{name='jack', age=2, color='蓝', friend=null}}
    8. --->sheep3 friend hashCode:692404036

    深拷贝

    1)复制对象的所有基本数据类型的成员变量值

    2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝

    3)深拷贝实现方式  1:重写clone方法来实现深拷贝

    4)深拷贝实现方式  2:通过对象序列化实现深拷贝(推荐)

    实现深拷贝之重写clone方法

    User类,作为另一个类的引用类型属性

    1. public class User implements Cloneable{
    2. public String name;
    3. public String address;
    4. public User(String name, String address) {
    5. this.name = name;
    6. this.address = address;
    7. }
    8. @Override
    9. protected Object clone() throws CloneNotSupportedException {
    10. return super.clone();
    11. }
    12. //toString省略
    13. }

    拷贝对象,引用User类

    1. public class DeepProtoType implements Cloneable {
    2. public String nation;
    3. public User user;
    4. //深拷贝 ;重写clone方法实现;
    5. @Override
    6. protected Object clone() throws CloneNotSupportedException {
    7. Object deep = null;
    8. //对String类型 进行处理;
    9. deep = super.clone();
    10. DeepProtoType deepProtoType =(DeepProtoType) deep;
    11. //对引用类型处理;
    12. deepProtoType.user= (User) user.clone();
    13. return deepProtoType;
    14. }
    15. //省略toString
    16. }

    克隆测试

    1. public class Client {
    2. public static void main(String[] args) throws CloneNotSupportedException {
    3. DeepProtoType deepProtoType = new DeepProtoType();
    4. deepProtoType.nation="中国";
    5. deepProtoType.user=new User("小卤蛋","陕西省");
    6. //方式一;
    7. DeepProtoType dp1 = (DeepProtoType) deepProtoType.clone();
    8. DeepProtoType dp2 = (DeepProtoType) deepProtoType.clone();
    9. //原来的;
    10. System.out.println(deepProtoType);
    11. System.out.println("user属性原哈希值"+deepProtoType.user.hashCode());
    12. //查看克隆后的;
    13. System.out.println("========克隆后;第一个========");
    14. System.out.println(dp1);
    15. System.out.println("user属性 哈希值"+dp1.user.hashCode());
    16. System.out.println("========克隆后;第二个========");
    17. System.out.println(dp2);
    18. System.out.println("user属性 哈希值"+dp2.user.hashCode());
    19. }
    20. }

     结果:

    1. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    2. user属性原哈希值692404036
    3. ========克隆后;第一个========
    4. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    5. user属性 哈希值1554874502
    6. ========克隆后;第二个========
    7. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    8. user属性 哈希值1846274136

     实现深拷贝之序列化对象法

     User类

    1. public class User implements Serializable{
    2. private static final long serialVersionUID = 1L;
    3. public String name;
    4. public String address;
    5. public User(String name, String address) {
    6. this.name = name;
    7. this.address = address;
    8. }
    9. //省略toString
    10. }

    拷贝对象,引用User类 

    1. public class DeepProtoType implements Serializable {
    2. private static final long serialVersionUID = 2L;
    3. public String nation;
    4. //引用类型
    5. public User user;
    6. //方式二
    7. public Object deepClone(){
    8. //创建流对象
    9. ByteArrayOutputStream bos = null;
    10. ByteArrayInputStream bis =null;
    11. ObjectOutputStream oos=null;
    12. ObjectInputStream ois =null;
    13. try {
    14. //序列化
    15. bos = new ByteArrayOutputStream();
    16. oos=new ObjectOutputStream(bos);
    17. oos.writeObject(this);//当前这个对象以对象流的方式输出
    18. //反序列化
    19. bis=new ByteArrayInputStream(bos.toByteArray());
    20. ois=new ObjectInputStream(bis);
    21. DeepProtoType copyObj=(DeepProtoType)ois.readObject();
    22. return copyObj;
    23. } catch (Exception e) {
    24. e.printStackTrace();
    25. return null;
    26. }finally {
    27. try {
    28. bos.close();
    29. oos.close();
    30. bis.close();
    31. ois.close();
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. }
    37. //省略toString
    38. }

    克隆测试

    1. public class Client {
    2. public static void main(String[] args) throws CloneNotSupportedException {
    3. DeepProtoType deepProtoType = new DeepProtoType();
    4. deepProtoType.nation="中国";
    5. deepProtoType.user=new User("小卤蛋","陕西省");
    6. //方式一;
    7. DeepProtoType dp1 = (DeepProtoType) deepProtoType.deepClone();
    8. DeepProtoType dp2 = (DeepProtoType) deepProtoType.deepClone();
    9. //原来的;
    10. System.out.println(deepProtoType);
    11. System.out.println("user属性原哈希值"+deepProtoType.user.hashCode());
    12. //查看克隆后的;
    13. System.out.println("========克隆后;第一个========");
    14. System.out.println(dp1);
    15. System.out.println("user属性 哈希值"+dp1.user.hashCode());
    16. System.out.println("========克隆后;第二个========");
    17. System.out.println(dp2);
    18. System.out.println("user属性 哈希值"+dp2.user.hashCode());
    19. }
    20. }

    结果:

    1. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    2. user属性原哈希值1725154839
    3. ========克隆后;第一个========
    4. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    5. user属性 哈希值883049899
    6. ========克隆后;第二个========
    7. DeepProtoType{nation='中国', user=User{name='小卤蛋', address='陕西省'}}
    8. user属性 哈希值2093176254

    二、原型模式在 Spring 框架中源码分析

    Spring 中原型 bean 的创建,就是原型模式的应用

    写个实体类

    1. public class Book {
    2. private String name;
    3. private int id;
    4. private String author;
    5. //省略get,set,toString方法
    6. }

    bean.xml文件

    作用类型切换为scope=“prototype”

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    5. <bean id="book" class="prototype_03.Book" scope="prototype">
    6. <property name="author" value="卤蛋"/>
    7. <property name="id" value="2"/>
    8. <property name="name" value="java"/>
    9. bean>
    10. beans>

     测试

    1. public class Test {
    2. public static void main(String[] args) {
    3. ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("prototype_03/bean1.xml");
    4. Book book1 = (Book)applicationContext.getBean("book");
    5. Book book2 = (Book)applicationContext.getBean("book");
    6. System.out.println(book1);
    7. System.out.println(book2);
    8. System.out.println(book1 == book2);
    9. }
    10. }

    运行结果

    Book{name='java', id=2, auuthor='卤蛋'}
    Book{name='java', id=2, auuthor='卤蛋'}
    false 

    debug调试 

     

    三、原型模式的注意事项和细节

    1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率

    2)不用重新初始化对象,而是动态地获得对象运行时的状态

    3)如果原始对象发生变化(增加或者减少属性),其它克隆对象也会发生相应的变化,无需修改代码

    4)在实现深克隆的时候可能需要比较复杂的代码

    缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了  ocp  原则,这点需要注意。 

  • 相关阅读:
    【AGC】云存储如何上传文件?是否可以自行开通?云存储的相关问题,来这里看看!
    「PAT甲级真题解析」Advanced Level 1009 Product of Polynomials
    C# 获取当前年月日星期第几周等信息
    文章参考链接
    JVM基础:初识JVM
    屏幕录制视频编辑软件 Camtasia 2023 mac中文版软件功能
    工厂人员工装穿戴检测系统
    【Mysql-索引的底层结构】
    【C++】运算符重载 ⑧ ( 左移运算符重载 | 友元函数 / 成员函数 实现运算符重载 | 类对象 使用 左移运算符 )
    苹果商城(App Store)应用程序苹果ios签名进行系统怎么上架的注意事项完整教程
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/126845924