介绍

核心作用

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

优缺点

原型模式的优点:

  1. Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  2. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  1. 需要为每一个类都配置一个 clone 方法。
  2. clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  3. 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

    原型模式的结构与实现

    由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

    模式的结构

    原型模式包含以下主要角色。

  4. 抽象原型类:规定了具体原型对象必须实现的接口。

  5. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  6. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

    模式的实现

    原型模式的克隆分为浅克隆和深克隆。
  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java对象复制(克隆)的三种方式

  1. 直接赋值复制
  2. 浅复制(复制引用但不复制引用的对象)
  3. 深复制(复制对象和其应用对象)

    对象

    ```java package top.simba1949.prototype;

import lombok.Data;

/**

  • @author anthony
  • @date 2022/2/22 */ @Data public class User implements Cloneable { private String username; private int age; private Role role;

    @Override protected Object clone() throws CloneNotSupportedException {

    1. return super.clone();

    } } java package top.simba1949.prototype;

import lombok.Data;

/**

  • @author anthony
  • @date 2022/2/22 */ @Data public class Role { private String roleName; } ```

    直接赋值复制

    实际上复制的是对象的引用,任何一个实例的数据变动都会引起另外一个实例的数据变动

  1. User user1 = new User();
  2. user1.setAge(1);
  3. User user2 = user1;
  4. log.info("user1 == user2 ? {}", user1 == user2); // true
  5. log.info("user1={}, user2={}", user1, user2);
  6. // user1=User(username=null, age=1, role=null), user2=User(username=null, age=1, role=null)
  7. user1.setAge(11);
  8. log.info("user1={}, user2={}", user1, user2);
  9. // user1=User(username=null, age=11, role=null), user2=User(username=null, age=11, role=null)
  10. user2.setAge(2);
  11. log.info("user1={}, user2={}", user1, user2);
  12. // user1=User(username=null, age=2, role=null), user2=User(username=null, age=2, role=null)

浅复制(复制引用但不复制引用的对象)

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象;

在需要克隆的对象里,实现Cloneable接口,重写clone方法;

  1. Role role = new Role();
  2. role.setRoleName("roleName");
  3. User user1 = new User();
  4. user1.setAge(1);
  5. user1.setUsername("username1");
  6. user1.setRole(role);
  7. User user2 = (User) user1.clone();
  8. log.info("user1 == user2 ? {}", user1 == user2); // false
  9. log.info("user1={}, user2={}", user1, user2);
  10. // user1=User(username=username1, age=1, role=Role(roleName=roleName)), user2=User(username=username1, age=1, role=Role(roleName=roleName))
  11. user1.setAge(11);
  12. user1.setUsername("username11");
  13. user1.getRole().setRoleName("roleName1");
  14. log.info("user1={}, user2={}", user1, user2);
  15. // user1=User(username=username11, age=11, role=Role(roleName=roleName1)), user2=User(username=username1, age=1, role=Role(roleName=roleName1))
  16. user2.setAge(2);
  17. user2.setUsername("username2");
  18. user2.getRole().setRoleName("roleName2");
  19. log.info("user1={}, user2={}", user1, user2);
  20. // user1=User(username=username11, age=11, role=Role(roleName=roleName2)), user2=User(username=username2, age=2, role=Role(roleName=roleName2))

深复制(复制对象和其应用对象)

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

第一种方式-将对象内所有引用的对象重写Clone方法

  1. 在引用对象里,实现Cloneable接口,重写clone方法;
  2. 在需要克隆的对象里,实现Cloneable接口,重写clone方法;clone方法中需要特殊处理;
  1. package top.simba1949.prototype;
  2. import lombok.Data;
  3. /**
  4. * @author anthony
  5. * @date 2022/2/22
  6. */
  7. @Data
  8. public class Role implements Cloneable{
  9. private String roleName;
  10. @Override
  11. protected Object clone() throws CloneNotSupportedException {
  12. return super.clone();
  13. }
  14. }
  1. package top.simba1949.prototype;
  2. import lombok.Data;
  3. /**
  4. * @author anthony
  5. * @date 2022/2/22
  6. */
  7. @Data
  8. public class User implements Cloneable {
  9. private String username;
  10. private int age;
  11. private Role role;
  12. @Override
  13. protected Object clone() throws CloneNotSupportedException {
  14. User clone = (User) super.clone();
  15. // 特殊处理
  16. clone.setRole((Role) this.role.clone());
  17. return clone;
  18. }
  19. }
  1. package top.simba1949.prototype;
  2. import lombok.extern.slf4j.Slf4j;
  3. /**
  4. * @author anthony
  5. * @date 2022/2/22
  6. */
  7. @Slf4j
  8. public class PrototypeApp {
  9. public static void main(String[] args) throws CloneNotSupportedException {
  10. Role role = new Role();
  11. role.setRoleName("roleName");
  12. User user1 = new User();
  13. user1.setAge(1);
  14. user1.setUsername("username1");
  15. user1.setRole(role);
  16. User user2 = (User) user1.clone();
  17. log.info("user1 == user2 ? {}", user1 == user2); // false
  18. log.info("user1={}, user2={}", user1, user2);
  19. // user1=User(username=username1, age=1, role=Role(roleName=roleName)), user2=User(username=username1, age=1, role=Role(roleName=roleName))
  20. user1.setAge(11);
  21. user1.setUsername("username11");
  22. user1.getRole().setRoleName("roleName11");
  23. log.info("user1={}, user2={}", user1, user2);
  24. // user1=User(username=username11, age=11, role=Role(roleName=roleName11)), user2=User(username=username1, age=1, role=Role(roleName=roleName))
  25. user2.setAge(2);
  26. user2.setUsername("username2");
  27. user2.getRole().setRoleName("roleName2");
  28. log.info("user1={}, user2={}", user1, user2);
  29. // user1=User(username=username11, age=11, role=Role(roleName=roleName11)), user2=User(username=username2, age=2, role=Role(roleName=roleName2))
  30. }
  31. }

第二种方式-反序列化

实现Serializable接口,补填serialVersionUID即可

  1. package top.simba1949.prototype;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. /**
  5. * @author anthony
  6. * @date 2022/2/22
  7. */
  8. @Data
  9. public class User implements Serializable {
  10. private static final long serialVersionUID = 8419563494877803769L;
  11. private String username;
  12. private int age;
  13. private Role role;
  14. }
  1. package top.simba1949.prototype;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. /**
  5. * @author anthony
  6. * @date 2022/2/22
  7. */
  8. @Data
  9. public class Role implements Serializable {
  10. private static final long serialVersionUID = -6434468911599252316L;
  11. private String roleName;
  12. }
  1. package top.simba1949.prototype;
  2. import com.alibaba.fastjson.JSON;
  3. import lombok.extern.slf4j.Slf4j;
  4. /**
  5. * @author anthony
  6. * @date 2022/2/22
  7. */
  8. @Slf4j
  9. public class PrototypeApp {
  10. public static void main(String[] args) {
  11. Role role = new Role();
  12. role.setRoleName("roleName");
  13. User user1 = new User();
  14. user1.setAge(1);
  15. user1.setUsername("username1");
  16. user1.setRole(role);
  17. String user1Str = JSON.toJSONString(user1);
  18. User user2 = JSON.parseObject(user1Str, User.class);
  19. log.info("user1 == user2 ? {}", user1 == user2); // false
  20. log.info("user1={}, user2={}", user1, user2);
  21. // user1=User(username=username1, age=1, role=Role(roleName=roleName)), user2=User(username=username1, age=1, role=Role(roleName=roleName))
  22. user1.setAge(11);
  23. user1.setUsername("username11");
  24. user1.getRole().setRoleName("roleName11");
  25. log.info("user1={}, user2={}", user1, user2);
  26. // user1=User(username=username11, age=11, role=Role(roleName=roleName11)), user2=User(username=username1, age=1, role=Role(roleName=roleName))
  27. user2.setAge(2);
  28. user2.setUsername("username2");
  29. user2.getRole().setRoleName("roleName2");
  30. log.info("user1={}, user2={}", user1, user2);
  31. // user1=User(username=username11, age=11, role=Role(roleName=roleName11)), user2=User(username=username2, age=2, role=Role(roleName=roleName2))
  32. }
  33. }