0.参考资料



1.概述

  • 使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。—《设计模式》GoF
    • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,

无需知道如何创建的细节

  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建

的对象通过请求原型对象拷贝它们自己来实施创建,如 对象.clone()

  • 核心:

1.1动机

  1. - 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
  2. - 如何应对这种变化?如何向"客户程序(使用这些对象的程序)" 隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

1.2结构

  1. - ![Q8_%P9MUY7FU8$C5Y@Q2HXW.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628687578262-4354dd23-1a10-4f31-b502-f397def427cb.png#clientId=ude33fbc5-3439-4&from=paste&height=283&id=u2328ddb3&margin=%5Bobject%20Object%5D&name=Q8_%25P9MUY7FU8%24C5Y%40Q2HXW.png&originHeight=565&originWidth=1217&originalType=binary&ratio=1&size=92342&status=done&style=none&taskId=u7cb2698c-bbd1-49c7-91be-7a4305c5776&width=608.5)

2.要点总结

  • 宏观上
    1. Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些 “易变类”拥有“稳定的接口”。
    2. Prototype模式对于“如何创建易变类的实体对象”采用”原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象一-所需 工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方Clone。
    3. Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
  • 微观代码上

    1. 创建新的对象比较复杂时,可以利用原型模式避免重复初始化来简化对象的创建过程,同时也能够提高效率
    2. 不用重新初始化对象,而是动态地获得对象运行时的状态
    3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
    4. 在实现深克隆的时候可能需要比较复杂的代码
    5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则

      3.案例

      需求

      • 克隆羊

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

          方案

      • 硬编码 ```java

public class Demo1 { public static void main(String[] args) {

  1. Sheep mySheep = new Sheep("tom", 10);
  2. Sheep sheep1 = new Sheep(mySheep.getName(), mySheep.getAge());
  3. Sheep sheep2 = new Sheep(mySheep.getName(), mySheep.getAge());
  4. Sheep sheep3 = new Sheep(mySheep.getName(), mySheep.getAge());
  5. System.out.println(mySheep);
  6. System.out.println(sheep1);
  7. System.out.println(sheep2);
  8. System.out.println(sheep3);
  9. }

}

@Data @AllArgsConstructor public class Sheep {

  1. String name;
  2. Integer age;

}

  1. <a name="Teio7"></a>
  2. ## 分析
  3. - 优缺点:
  4. - 优点是比较好理解,简单易操作。
  5. - 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂 时,效率较低
  6. - 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
  7. - 改进思路
  8. - Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以 将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口**Cloneable**, 该接口表示该类能够复制且具有复制的能力 => 原型模式
  9. <a name="ljftI"></a>
  10. # 4.使用模式
  11. <a name="y8ceq"></a>
  12. #### 方案
  13. - 使用**原型模式**改进传统方式,让程序具有更高的效率和扩展性。
  14. <a name="t4ODn"></a>
  15. #### 类图
  16. ...
  17. <a name="vHb6W"></a>
  18. #### 代码
  19. <a name="HROyd"></a>
  20. ##### 版本1: 简单的无引用类型属性(不包括String和包装类)
  21. - 代码
  22. ```java
  23. public class Demo2 {
  24. public static void main(String[] args) {
  25. Sheep mySheep = new Sheep("tom", 10);
  26. Sheep sheep1 = (Sheep)mySheep.clone();
  27. Sheep sheep2 = (Sheep)mySheep.clone();
  28. Sheep sheep3 = (Sheep)mySheep.clone();
  29. System.out.println(mySheep);
  30. System.out.println(sheep1);
  31. System.out.println(sheep2);
  32. System.out.println(sheep3);
  33. System.out.println(mySheep == sheep1);
  34. System.out.println(mySheep == sheep3);
  35. }
  36. }
  37. @Data
  38. @NoArgsConstructor
  39. @AllArgsConstructor
  40. public class Sheep implements Cloneable{
  41. String name;
  42. Integer age;
  43. @Override
  44. protected Object clone() {
  45. Sheep temp = null;
  46. try {
  47. temp = (Sheep)super.clone();
  48. } catch (CloneNotSupportedException e) {
  49. e.printStackTrace();
  50. }
  51. return temp;
  52. }
  53. }
  1. - 测试结果
  1. Sheep(name=tom, age=10)
  2. Sheep(name=tom, age=10)
  3. Sheep(name=tom, age=10)
  4. Sheep(name=tom, age=10)
  5. false
  6. false
  1. - 结论
  2. - clone() 返回的是一个属性的值与原型一样的对象(Java中只有值传递).
  3. - 这两个对象的空间指向不一样(== false)

版本2: 属性中有普通的引用类型
  1. - 代码
  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class Sheep implements Cloneable {
  5. String name;
  6. Integer age;
  7. Address address;
  8. @Override
  9. protected Object clone() {
  10. Sheep temp = null;
  11. try {
  12. temp = (Sheep) super.clone();
  13. } catch (CloneNotSupportedException e) {
  14. e.printStackTrace();
  15. }
  16. return temp;
  17. }
  18. }
  19. // 新增引用类型属性
  20. @Data
  21. @AllArgsConstructor
  22. @ToString
  23. public class Address implements Cloneable{
  24. private Provices provices;
  25. @Override
  26. protected Object clone() throws CloneNotSupportedException {
  27. return super.clone();
  28. }
  29. }
  30. @Data
  31. @AllArgsConstructor
  32. @ToString
  33. class Provices implements Cloneable{
  34. String provicesName;
  35. City city;
  36. }
  37. @Data
  38. @AllArgsConstructor
  39. @ToString
  40. class City implements Cloneable{
  41. String cityName;
  42. }
  43. // 测试类
  44. public class Demo2 {
  45. public static void main(String[] args) {
  46. Sheep mySheep = new Sheep("tom", 10, new Address(new Provices("江西" ,new City("抚州"))));
  47. Sheep sheep1 = (Sheep)mySheep.clone();
  48. Sheep sheep2 = (Sheep)mySheep.clone();
  49. Sheep sheep3 = (Sheep)mySheep.clone();
  50. System.out.println("修改前");
  51. System.out.println(mySheep);
  52. System.out.println(sheep1);
  53. System.out.println(sheep2);
  54. System.out.println(sheep3);
  55. sheep3.getAddress().getProvices().setCity(new City("九江"));
  56. System.out.println("修改后");
  57. System.out.println(mySheep);
  58. System.out.println(sheep1);
  59. System.out.println(sheep2);
  60. System.out.println(sheep3);
  61. System.out.println(mySheep == sheep1);
  62. System.out.println(mySheep == sheep3);
  63. }
  64. }
  - 测试结果
修改前
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
修改后
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
false
false
  - 结论
     - 对于属性中有引用类型的对象的 clone(),  因为原始的 clone()是值拷贝, 虽然确实已经开辟了新的堆空间( == 判断为false), 但是堆空间中的值引用还是值拷贝--即和原型一致.改变任意一个实例对象的引用属性, 其它实例对象包括原型的对应的引用属性都会被改变(即引用一致)  -->浅拷贝
     - 想要完善上面的问题, 应进行[深拷贝](#KZycN). 
     - 与深拷贝的对比

5.经典使用


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

5.2源码分析

     1. beans.xml配置

:::info

_ :::

     2. 测试类
{
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

    //获取monster[通过id获取monster]
    Object bean = applicationContext.getBean("id01");
    System.out.println("bean" + bean)
}
     3. getBean(...)方法
        - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628691152713-8affd658-30d6-437e-980d-acd4f3d86481.png#clientId=ude33fbc5-3439-4&from=paste&height=52&id=u08b0e289&margin=%5Bobject%20Object%5D&name=image.png&originHeight=104&originWidth=613&originalType=binary&ratio=1&size=8674&status=done&style=none&taskId=u5420ac31-f457-4d77-a671-1e5b8e7a0bd&width=306.5)
     4. 底层调用, 创造原型对象
        - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628691160576-0833e1ba-238e-463e-b896-7d2d31146c1b.png#clientId=ude33fbc5-3439-4&from=paste&height=218&id=ufba44bbc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=435&originWidth=822&originalType=binary&ratio=1&size=52957&status=done&style=none&taskId=u707bdaaf-6163-4e0a-b011-dbc111066d5&width=411)

6.Java对象拷贝中的深拷贝和浅拷贝


6.1对象拷贝

6.1.1示例

Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
blog.Teacher@355da254
blog.Teacher@4dc63996

6.1.2概述

     - 由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
     - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628755782248-d13dfdd4-d7bf-494e-8842-5ad7b65276f5.png#clientId=u0e86ce5a-55c7-4&from=paste&height=276&id=uf091e574&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=913&originalType=url&ratio=1&size=30849&status=done&style=none&taskId=uc20ff949-b1ba-42d4-ac40-bbc4b469adc&width=457)

6.2浅拷贝

6.2.1示例

     - 代码
public class ShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {

        // 原型中的引用对象
        Teacher teacher = new Teacher();
        teacher.setName("Delacey");
        teacher.setAge(29);

        // 原型对象
        Student2 student1 = new Student2();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        // 对原型对象进行对象(浅拷贝)
        Student2 student2 = (Student2) student1.clone();
        System.out.println("拷贝后");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());

        // 修改老师的信息
        teacher.setName("Jam");

        System.out.println("修改老师的姓名为Jam后-------------");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
    }

}

@Data
class Teacher implements Cloneable {
    private String name;
    private int age;
}

@Data
class Student2 implements Cloneable{
    private String name;
    private int age;
    private Teacher teacher;

    @Override
    public Object clone() throws CloneNotSupportedException {
        Object object = super.clone();
        return object;
    }

}
     - 测试结果
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam
     - 结果分析
        -  两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
        - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628756240624-7ab3acf3-5a33-416b-8433-565c925a01b1.png#clientId=uccac0922-6a1b-4&from=paste&height=385&id=u15830f9a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=769&originWidth=1394&originalType=url&ratio=1&size=73272&status=done&style=none&taskId=ud12c3239-b357-42dd-9730-cbdab28ec99&width=697)

6.2.2概述

     - 原型对象实现cloneable 并重写 clone().
     - 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享。
     - 简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象

6.3深拷贝

6.2.1示例(Clone()方法)

     - 代码
public class DeepCopy {
    public static void main(String[] args) throws Exception {

        //原型对象中的引用属性
        Teacher2 teacher = new Teacher2();
        teacher.setName("Delacey");
        teacher.setAge(29);

        //原型对象
        Student3 student1 = new Student3();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        //深拷贝对象
        Student3 student2 = (Student3) student1.clone();
        System.out.println("拷贝后");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("修改老师的信息后-------------");

        // 修改老师的信息
        teacher.setName("Jam");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
    }
}

@Data
class Teacher2 implements Cloneable {
    private String name;
    private int age;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

@Data
class Student3 implements Cloneable {
    private String name;
    private int age;
    private Teacher2 teacher;

    @Override
    public Object clone() throws CloneNotSupportedException {
        // 浅复制时:
        // Object object = super.clone();
        // return object;

        // 改为深复制:
        Student3 student = (Student3) super.clone();
        // 本来是浅复制,现在将Teacher对象复制一份并重新set进来
        student.setTeacher((Teacher2) student.getTeacher().clone());
        return student;
    }

}
     - 测试结果
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey
     - 结果分析
        - 两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的teacher属性引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝。
        - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628756505682-38330bc8-d9cb-4403-9640-81516c938059.png#clientId=uccac0922-6a1b-4&from=paste&height=367&id=u8d2b04a3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=734&originWidth=1327&originalType=url&ratio=1&size=65490&status=done&style=none&taskId=uf29b1ee6-e010-44e4-ae65-855c394e25d&width=664)

6.2.2概述

     - 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
     - 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

6.2.3深拷贝的两种方法

     1. 实现cloneable 接口的clone()方法: [简单的clone()实现浅拷贝](#ljftI) 可以看出, 若想用clone()实现深拷贝, 则原型中的所有(引用类型)属性及其属性类中的(引用类型)属性都应该实现cloneable 接口并重写clone()方法(层层嵌套), 并且在逐层往上的clone()中进行调用对应属性的clone(). 即多层克隆问题, 非常不方便. 不建议使用.
     1. 实现Serializable 接口序列化: 可以解决多层克隆的问题. 要求所有的对象对实现 Serializable 接口, 以序列化写入/读取到内存中.

6.2.4使用深拷贝解决克隆羊的问题

     - 代码

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Serializable {

    String name;
    Integer age;
    Address address;

    // 测试方法
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 原型对象
        Sheep prototype = new Sheep("tom", 10, new Address(new Provices("江西", new City("抚州"))));

        Sheep sheep1 = (Sheep)deepClone(prototype);
        Sheep sheep2 = (Sheep)deepClone(prototype);
        Sheep sheep3 = (Sheep)deepClone(prototype);

        System.out.println("修改前");
        System.out.println(prototype);
        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);

        sheep1.getAddress().setProvices(new Provices("北京", new City("紫禁城")));
        sheep3.getAddress().getProvices().setCity(new City("九江"));

        System.out.println("修改后");
        System.out.println(prototype);
        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);

        System.out.println(prototype == sheep1);
        System.out.println(prototype == sheep3);
    }

    // 深拷贝方法
    public static Object deepClone(Sheep sheep) throws IOException, ClassNotFoundException {

        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(sheep);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }
}
     - 测试结果
修改前
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
修改后
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=北京, city=City(cityName=紫禁城))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=抚州))))
Sheep(name=tom, age=10, address=Address(provices=Provices(provicesName=江西, city=City(cityName=九江))))
false
false
     - 分析
        - 每一个拷贝出来的都是一个个体, 包括其内部包含的引用属性, 与原型对象和其它的拷贝对象互不干扰.
     - 好处
        - 只需要每个属性实现 序列化Serializable接口(类比clone()方法中, 都需要实现cloneable接口),  而不再需要重写clone(), 不用处理多层克隆问题.