定义

原型模式(Prototype Pattern) 是指用一个已经创建好的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

使用场景

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

  • 类初始化消耗资源较多
  • new 产生的一个对象需要非常繁琐的过程
  • 构造函数复杂
  • 循环体中生产大量对象是

在Spring中原型模式应用非常广泛,我们只需要在scope上指定prototype即使用了原型模式。

结构与实现

3-1Q114101Fa22.gif
原型模式主要包含以下角色:

  1. 抽象原型类:规定了具体原型对象必须实现的接口。
  2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

原型接口定义

  1. public interface IPrototype {
  2. //克隆方法,克隆出一个自己出来
  3. IPrototype clone();
  4. }

具体实现

  1. /**
  2. * 实现IPrototype接口
  3. */
  4. public class SunWuKong implements IPrototype {
  5. //武力值
  6. private int power;
  7. //技能
  8. private List<String> skills;
  9. public int getPower() {
  10. return power;
  11. }
  12. public void setPower(int power) {
  13. this.power = power;
  14. }
  15. public List<String> getSkills() {
  16. return skills;
  17. }
  18. public void setSkills(List<String> skills) {
  19. this.skills = skills;
  20. }
  21. /**
  22. *
  23. * 实现clone方法
  24. * @return
  25. */
  26. @Override
  27. public SunWuKong clone() {
  28. SunWuKong sunWuKong = new SunWuKong();
  29. sunWuKong.setPower(this.power);
  30. sunWuKong.setSkills(this.skills);
  31. return sunWuKong;
  32. }
  33. @Override
  34. public String toString() {
  35. return "{\"power\":" + this.power + ",\"skills\":" + Arrays.toString(this.skills.toArray()) + "}";
  36. }
  37. }

客户端

  1. public class PrototypeTest {
  2. public static void main(String[] args) {
  3. SunWuKong sunWuKong = new SunWuKong();
  4. sunWuKong.setPower(100);
  5. List<String> skills =new ArrayList<>();
  6. skills.add("筋斗云");
  7. skills.add("七十二变");
  8. sunWuKong.setSkills(skills);
  9. SunWuKong sunWuKong1 = sunWuKong.clone();
  10. sunWuKong1.getSkills().add("金箍棒");
  11. System.out.println("拷贝出来的对象和原对象是否是同一个对象:"+ (sunWuKong == sunWuKong1));
  12. System.out.println("拷贝出来的对象中的引用属性对象和原对象中的引用属性对象是否相等:"+ (sunWuKong.getSkills() == sunWuKong1.getSkills()));
  13. System.out.println(sunWuKong.toString()+"===="+sunWuKong1.toString());
  14. }
  15. }

输出结果
image.png
从上面的原型示例中我们很容易发现一个问题,那就是虽然克隆出来的对象和原对象不是同一个,但是其引用类型的对象却是使用的同一个,这种情况称之为浅克隆
在Java语言中已经提供了clone方法,它定义在Object类中,如果需要克隆某个类,只需要让这个类实现java.lang.Cloneable接口,这个接口没有需要实现的方法,是一个标识接口。Java中提供的clone方法是一个浅克隆。
下面我们利用Java自带的clone方法来重写上面的示例:

  1. public class SunWuKong implements Cloneable {
  2. //武力值
  3. private int power;
  4. //技能
  5. private List<String> skills;
  6. public int getPower() {
  7. return power;
  8. }
  9. public void setPower(int power) {
  10. this.power = power;
  11. }
  12. public List<String> getSkills() {
  13. return skills;
  14. }
  15. public void setSkills(List<String> skills) {
  16. this.skills = skills;
  17. }
  18. /**
  19. * 直接调用父类即Object的clone方法
  20. *
  21. * @return
  22. */
  23. @Override
  24. protected SunWuKong clone() throws CloneNotSupportedException {
  25. return (SunWuKong) super.clone();
  26. }
  27. @Override
  28. public String toString() {
  29. return "{\"power\":" + this.power + ",\"skills\":" + Arrays.toString(this.skills.toArray()) + "}";
  30. }
  31. }

上面代码中,只是将接口替换成了Java自带的Cloneable接口,并且真正干活的是父类的clone方法。下面我们来测试看看,测试代码任然是上面的代码。
image.png
从结果中我们也证明了Java中的clone方法是一个浅克隆。

浅克隆与深克隆

既然有浅克隆那么肯定有深克隆了,深克隆即使将原对象中的引用类型也都克隆一份,使得克隆出来的对象中的引用类型与原对象中的引用类型不是指向同一块内存空间。
深克隆一般使用序列化来实现,我们将上面原型实例改造下,代码如下:

  1. public class SunWuKong implements Serializable {
  2. //武力值
  3. private int power;
  4. //技能
  5. private List<String> skills;
  6. public int getPower() {
  7. return power;
  8. }
  9. public void setPower(int power) {
  10. this.power = power;
  11. }
  12. public List<String> getSkills() {
  13. return skills;
  14. }
  15. public void setSkills(List<String> skills) {
  16. this.skills = skills;
  17. }
  18. /**
  19. *
  20. * @return
  21. */
  22. public SunWuKong clone() {
  23. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  24. try {
  25. ObjectOutputStream oos = new ObjectOutputStream(bos);
  26. oos.writeObject(this);
  27. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  28. ObjectInputStream ois = new ObjectInputStream(bis);
  29. SunWuKong sunWuKong = (SunWuKong) ois.readObject();
  30. return sunWuKong;
  31. } catch (IOException | ClassNotFoundException e) {
  32. e.printStackTrace();
  33. }
  34. return null;
  35. }
  36. @Override
  37. public String toString() {
  38. return "{\"power\":" + this.power + ",\"skills\":" + Arrays.toString(this.skills.toArray()) + "}";
  39. }
  40. }

运行测试代码,结果如下:
image.png
从结果中我们可以看到,利用序列化克隆出来的对象,不管是对象本身还是其引用类的属性都不是指向同一个对象了,即实现了克隆出来的对象和原对象完全分离,这就是深克隆。
除了使用序列化来实现深克隆外,我们还可以利用Java自带的clone方法来实现深克隆,只需要在克隆出来的对象上将其每一个引用类型的属性再次克隆一份即可。示例如下:

  1. @Override
  2. public SunWuKong4Java clone() throws CloneNotSupportedException {
  3. SunWuKong4Java sunWuKong4Java = (SunWuKong4Java) super.clone();
  4. sunWuKong4Java.setSkills((ArrayList<String>) this.skills.clone());
  5. return sunWuKong4Java;
  6. }

原型模式的优缺点

  • 优点:
    • 原型模式简化了对象的创建过程。
    • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 缺点:
    • 每个类必须配置一个clone方法。
    • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
    • 克隆方法位于类的内部,当对已有类进行改造时,违反了开闭原则。