概述

原型模式:Prototype Pattern,指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创建型设计模式

原型模式的核心在于复制原型对象

  • 以系统中已存在的一个对象为原型,直接基于内存二进制流进行复制,不需要再经历耗时的对象初始化过程(不调用构造函数),能够提升对象复制的性能
  • 当对象的构建过程比较耗时时,可以把当前系统中已存在的对象作为原型,对其进行复制(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大缩短

应用场景

原型模式主要适用于以下应用场景:

  • 创建对象成本较大,需要优化资源,如:初始化时间长,占用CPU太多,或者占用网络资源太多等
  • 创建一个对象需要烦琐的数据准备或访问权限等,需要提高性能或者提高安全性
  • 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值

在 Spring 中,原型模式应用得非常广泛,例如 scope=”prototype”、JSON.parseObject(),都是原型模式的具体应用

原型模式主要包含 2 个角色:

  • 抽象原型(IPrototype):规定复制接口
  • 具体原型(ConcretePrototype):被复制的对象

注意:不是通过 new 关键字而是通过对象赋值来实现创建对象的模式被称作原型模式

  1. public class PrototypeDemo {
  2. public static void main(String[] args) {
  3. // 创建原型对象
  4. ConcretePrototype prototype = new ConcretePrototype("原型");
  5. ConcretePrototype clone = prototype.clone();
  6. System.out.println(clone);
  7. }
  8. // 抽象原型
  9. interface IPrototype<T> {
  10. T clone();
  11. }
  12. // 具体原型
  13. static class ConcretePrototype implements IPrototype<ConcretePrototype> {
  14. private String desc;
  15. public ConcretePrototype(String desc) {
  16. this.desc = desc;
  17. }
  18. @Override
  19. public ConcretePrototype clone() {
  20. return new ConcretePrototype(this.desc);
  21. }
  22. @Override
  23. public String toString() {
  24. return "ConcretePrototype{" +
  25. "desc='" + desc + '\'' +
  26. '}';
  27. }
  28. }
  29. }

原型模式的应用

使用:在 java 中,无需手动创建原型接口,已经内置了 Cloneable 抽象原型接口,自定义的类型只需要实现该接口并重写 Object.clone() 方法即可完成本类的复制

Cloneable 接口是一个空接口,提供该接口的作用:

  • 为了在运行时通知 java 虚拟机可以安全地该类上使用 clone() 方法
  • 如果该类没有实现 Cloneable 接口,调用 clone() 方法会抛出 CloneNotSupportedException 异常

一般情况下,使用 clone() 需要满足以下条件:

  • 对任何对象 o,都有 o.clone() != o,即克隆对象与原型对象不是同一个对象
  • 对任何对象 o,都有 o.clone().getClass() == o.getClass(),即克隆对象与原型对象的类型一样
  • 如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立

在设计自定义类的 clone() 方法时,一般前 2 个方法是必需的,第 3 个是可选的

  1. public class PrototypeWithCloneable {
  2. public static void main(String[] args) {
  3. ConcretePrototype prototype = new ConcretePrototype("测试");
  4. ConcretePrototype clone = prototype.clone();
  5. System.out.println(clone);
  6. }
  7. static class ConcretePrototype implements Cloneable {
  8. private String desc;
  9. public ConcretePrototype(String desc) {
  10. this.desc = desc;
  11. }
  12. @Override
  13. protected ConcretePrototype clone() {
  14. ConcretePrototype cloneType = null;
  15. try {
  16. // super.clone() 方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此其效率很高
  17. // super.clone() 基于内存复制,属于浅拷贝
  18. cloneType = (ConcretePrototype) super.clone();
  19. } catch (CloneNotSupportedException e) {
  20. e.printStackTrace();
  21. }
  22. return cloneType;
  23. }
  24. @Override
  25. public String toString() {
  26. return "ConcretePrototype{" +
  27. "desc='" + desc + '\'' +
  28. '}';
  29. }
  30. }
  31. }

super.clone() 方法:

  • 优点:
    • 直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此其效率很高
    • 由于 super.clone() 方法基于内存复制,因此不会调用对象的构造函数,也就是不需要经历初始化过程
  • 缺点:
    • 属于浅拷贝,如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用
    • 如果需要实现深拷贝,需要在对象创建之后再对其中的属性一一进行拷贝,特别麻烦,因此一般使用序列化实现深拷贝

使用序列化实现深拷贝

通过序列化实现深拷贝的注意事项:

  • 被拷贝的对象必须实现 Serializable 接口,同时定义一个 private static final long serialVersionUID = 3760742309246179789L; 对象用于标识唯一的一个类,避免因为类的修改导致生成 serial VersionUID 不同而反序列化失败
  • 同样,被拷贝的对象如果存在对象类型的属性,那么这些必须同样实现 Serializable 接口,否则会报 NotSerializableException 异常

    1. /**
    2. * 深拷贝方法,通过序列化和非序列实现,因此,需要深拷贝的对象必须实现Serializable接口,
    3. * 且对象中如果存在对象引用,该对象引用所指向的对象也必须实现 Serializable,否则会抛出异常
    4. *
    5. * @param source 要深拷贝的对象
    6. * @param <T> 泛型
    7. * @return 拷贝的对象
    8. */
    9. public static <T> T deepCopy(T source) throws IOException, ClassNotFoundException {
    10. if (source instanceof Serializable) {
    11. // ByteArrayOutStream指向内存中的一个字节数组,用于保存序列化后的对象
    12. // 该流不需要进行关闭,因为当不需要使用时,GC会自动回收该对象
    13. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    14. // 创建对象输出流
    15. ObjectOutputStream oos = new ObjectOutputStream(bos);
    16. // 写入对象
    17. oos.writeObject(source);
    18. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    19. ObjectInputStream ois = new ObjectInputStream(bis);
    20. return (T) ois.readObject();
    21. }
    22. throw new NotSerializableException("被拷贝的对象必须实现Serializable接口!");
    23. }

    对深拷贝方法的测试:

    1. public class DeepCopyDemo {
    2. public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
    3. // 通过序列化和反序列实现深拷贝
    4. Demo demo = new Demo().setUser(new User().setName("李四").setAge(18));
    5. Demo deepCopy = DeepCopyDemo.deepCopy(demo);
    6. deepCopy.getUser().setAge(19);
    7. // 浅拷贝
    8. Demo clone = demo.clone();
    9. clone.getUser().setName("王五");
    10. System.out.println(demo);
    11. System.out.println(deepCopy);
    12. System.out.println(clone);
    13. }
    14. /**
    15. * 深拷贝方法,通过序列化和非序列实现,因此,需要深拷贝的对象必须实现Serializable接口,
    16. * 且对象中如果存在对象引用,该对象引用所指向的对象也必须实现 Serializable,否则会抛出异常
    17. *
    18. * @param source 要深拷贝的对象
    19. * @param <T> 泛型
    20. * @return 拷贝的对象
    21. */
    22. public static <T> T deepCopy(T source) throws IOException, ClassNotFoundException {
    23. if (source instanceof Serializable) {
    24. // ByteArrayOutStream指向内存中的一个字节数组,用于保存序列化后的对象
    25. // 该流不需要进行关闭,因为当不需要使用时,GC会自动回收该对象
    26. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    27. // 创建对象输出流
    28. ObjectOutputStream oos = new ObjectOutputStream(bos);
    29. // 写入对象
    30. oos.writeObject(source);
    31. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    32. ObjectInputStream ois = new ObjectInputStream(bis);
    33. return (T) ois.readObject();
    34. }
    35. throw new NotSerializableException("被拷贝的对象必须实现Serializable接口!");
    36. }
    37. @Data
    38. @Accessors(chain = true)
    39. static class User implements Serializable,Cloneable {
    40. private static final long serialVersionUID = 3760742309246179789L;
    41. private String name;
    42. private Integer age;
    43. @Override
    44. protected Object clone() throws CloneNotSupportedException {
    45. return super.clone();
    46. }
    47. }
    48. @Data
    49. @Accessors(chain = true)
    50. static class Demo implements Serializable, Cloneable {
    51. private static final long serialVersionUID = -1497612712921169083L;
    52. private User user;
    53. @Override
    54. protected Demo clone() throws CloneNotSupportedException {
    55. return (Demo) super.clone();
    56. }
    57. }
    58. }

    上述输出结果为:

    1. DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=王五, age=18))
    2. DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=李四, age=19))
    3. DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=王五, age=18))

结论:

  • 对于浅拷贝,只需要当前对象实现 Cloneable 接口,然后覆写 Object.clone() 方法即可,不要求对象中的对象属性也实现 Cloneable 接口
  • 对于序列化实现的深拷贝,需求当前类和类中对象属性都要实现 Serializable 接口,同时最好显示声明 serialVersionUID 接口,避免因类的修改导致反序列化失败