【却说那妖精与大圣斗经半日,不分胜败。行者把棒丢起,叫一声“变!”就以一变十,以十变百,以百变千,半天里,好似蛇游蟒搅,乱打妖邪。妖邪慌了手脚,将身一闪,化道清风,即奔碧空之上逃走。行者念声咒语,将铁棒收做一根,纵祥光一直赶来。】

在西游记第九十五回【假合真形擒玉兔 真阴归正会灵元】中,孙行者“殴打”玉兔精的时候,将如意金箍棒从一根化作了千百根,打得玉兔精无从招架。

这千百根金箍棒的属性应该是一样的,如果孙悟空每次都要新建一个新的金箍棒对象,然后把原有的金箍棒的属性复制过去,如此重复千百次,未免太过麻烦,所以我们这里假设孙悟空使用了原型模式来创建多个相同属性的金箍棒实例。

在详细介绍原型模式之前,我们需要先了解一下java.lang.Object#clone()方法以及java.lang.Cloneable接口的功能及实现:

java.lang.Cloneable

  1. /**
  2. * A class implements the <code>Cloneable</code> interface to
  3. * indicate to the {@link java.lang.Object#clone()} method that it
  4. * is legal for that method to make a
  5. * field-for-field copy of instances of that class.
  6. * <p>
  7. * Invoking Object's clone method on an instance that does not implement the
  8. * <code>Cloneable</code> interface results in the exception
  9. * <code>CloneNotSupportedException</code> being thrown.
  10. * <p>
  11. * By convention, classes that implement this interface should override
  12. * <tt>Object.clone</tt> (which is protected) with a public method.
  13. * See {@link java.lang.Object#clone()} for details on overriding this
  14. * method.
  15. * <p>
  16. * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
  17. * Therefore, it is not possible to clone an object merely by virtue of the
  18. * fact that it implements this interface. Even if the clone method is invoked
  19. * reflectively, there is no guarantee that it will succeed.
  20. *
  21. * @author unascribed
  22. * @see java.lang.CloneNotSupportedException
  23. * @see java.lang.Object#clone()
  24. * @since JDK1.0
  25. */
  26. public interface Cloneable {
  27. }

Java doc的意思大致是说:一个类实现了Cloneable接口,就是在运行时向虚拟机表明当前类可以合法地使用Object类的clone()方法,来进行对象内容的拷贝。假设没有实现Cloneable接口就调用clone()方法的话,虽然能够通过编译,但是会在运行时抛出java.lang.CloneNotSupportedException。一般来说,实现Cloneable接口的类需要重写Object类的protected方法,并且声明重写方法为public的。需要注意的是,Cloneable接口并不包含clone()方法。因此,一个类仅仅实现Cloneable接口就想成功实现clone()功能是不可能的。即使反射调用也不保证会成功。

也就是说,要想调用源生的Object类的clone()方法,我们必须让原型类实现Cloneable接口。那么,Object类的clone()的优势在哪里呢?

  1. /**
  2. * Creates and returns a copy of this object. The precise meaning
  3. * of "copy" may depend on the class of the object. The general
  4. * intent is that, for any object {@code x}, the expression:
  5. * <blockquote>
  6. * <pre>
  7. * x.clone() != x</pre></blockquote>
  8. * will be true, and that the expression:
  9. * <blockquote>
  10. * <pre>
  11. * x.clone().getClass() == x.getClass()</pre></blockquote>
  12. * will be {@code true}, but these are not absolute requirements.
  13. * While it is typically the case that:
  14. * <blockquote>
  15. * <pre>
  16. * x.clone().equals(x)</pre></blockquote>
  17. * will be {@code true}, this is not an absolute requirement.
  18. * <p>
  19. * By convention, the returned object should be obtained by calling
  20. * {@code super.clone}. If a class and all of its superclasses (except
  21. * {@code Object}) obey this convention, it will be the case that
  22. * {@code x.clone().getClass() == x.getClass()}.
  23. * <p>
  24. * By convention, the object returned by this method should be independent
  25. * of this object (which is being cloned). To achieve this independence,
  26. * it may be necessary to modify one or more fields of the object returned
  27. * by {@code super.clone} before returning it. Typically, this means
  28. * copying any mutable objects that comprise the internal "deep structure"
  29. * of the object being cloned and replacing the references to these
  30. * objects with references to the copies. If a class contains only
  31. * primitive fields or references to immutable objects, then it is usually
  32. * the case that no fields in the object returned by {@code super.clone}
  33. * need to be modified.
  34. * <p>
  35. * The method {@code clone} for class {@code Object} performs a
  36. * specific cloning operation. First, if the class of this object does
  37. * not implement the interface {@code Cloneable}, then a
  38. * {@code CloneNotSupportedException} is thrown. Note that all arrays
  39. * are considered to implement the interface {@code Cloneable} and that
  40. * the return type of the {@code clone} method of an array type {@code T[]}
  41. * is {@code T[]} where T is any reference or primitive type.
  42. * Otherwise, this method creates a new instance of the class of this
  43. * object and initializes all its fields with exactly the contents of
  44. * the corresponding fields of this object, as if by assignment; the
  45. * contents of the fields are not themselves cloned. Thus, this method
  46. * performs a "shallow copy" of this object, not a "deep copy" operation.
  47. * <p>
  48. * The class {@code Object} does not itself implement the interface
  49. * {@code Cloneable}, so calling the {@code clone} method on an object
  50. * whose class is {@code Object} will result in throwing an
  51. * exception at run time.
  52. *
  53. * @return a clone of this instance.
  54. * @throws CloneNotSupportedException if the object's class does not
  55. * support the {@code Cloneable} interface. Subclasses
  56. * that override the {@code clone} method can also
  57. * throw this exception to indicate that an instance cannot
  58. * be cloned.
  59. * @see java.lang.Cloneable
  60. */
  61. protected native Object clone() throws CloneNotSupportedException;

我们看到clone()方法是一个native方法,native方法的效率一般远高于非native方法。同时我们也可以看到关于clone()方法的描述也印证了Cloneable接口的相关介绍,如protected以及CloneNotSupportedException等。

关于clone()方法的表现如下:

x.clone() !=x;

x.clone().getClass() == x.getClass();

x.clone().equals(x) == true;

这里还要介绍关于深复制与浅复制的概念:

浅复制对象的所有属性都与原对象具有相同的值,包括引用其他对象的变量,对这些对象的引用依然指向原来的对象。

而深复制对象会将原对象的所有属性都复制一遍,包括原对象引用的对象,深复制会复制新的引用对象作为自己的变量而不使用原来的对象。

浅复制原型模式

  1. package com.tirion.design.prototype;
  2.  
  3. public class GoldenCudgel implements Cloneable {
  4.  
  5. public GoldenCudgel() {
  6.  
  7. }
  8.  
  9. public GoldenCudgel(boolean disappear) {
  10. this.disappear = disappear;
  11. }
  12.  
  13. private boolean disappear;
  14.  
  15. public boolean isDisappear() {
  16. return disappear;
  17. }
  18.  
  19. public void setDisappear(boolean disappear) {
  20. this.disappear = disappear;
  21. }
  22.  
  23. public GoldenCudgel clone() {
  24. GoldenCudgel goldenCudgel = null;
  25. try {
  26. goldenCudgel = (GoldenCudgel) super.clone();
  27. } catch (CloneNotSupportedException e) {
  28. e.printStackTrace();
  29. }
  30. return goldenCudgel;
  31. }
  32.  
  33. public boolean equals(GoldenCudgel obj) {
  34. return obj.isDisappear() == disappear;
  35. }
  36.  
  37. }

悟空

  1. package com.tirion.design.prototype;
  2.  
  3. public class WuKong {
  4.  
  5. private static GoldenCudgel goldenCudgel = new GoldenCudgel(false);
  6.  
  7. public static void main(String[] args) {
  8. GoldenCudgel copyGoldenCudgel = goldenCudgel.clone();
  9. System.out.println(goldenCudgel);
  10. System.out.println(copyGoldenCudgel);
  11. System.out.println(goldenCudgel != copyGoldenCudgel);
  12. System.out.println(goldenCudgel.getClass() == copyGoldenCudgel.getClass());
  13. System.out.println(goldenCudgel.equals(copyGoldenCudgel));
  14. }
  15.  
  16. }

打印结果

  1. com.tirion.design.prototype.GoldenCudgel@74a14482
  2. com.tirion.design.prototype.GoldenCudgel@1540e19d
  3. true
  4. true
  5. true

在金箍棒GoldenCudgel中,我们不仅提供了clone()方法的实现,还重写了queals()方法用于检验复制结果。

我们可以看到,孙悟空在创建新的金箍棒对象时,调用自身持有的金箍棒的clone()方法,就得到了一个新的金箍棒对象,它的属性值disappear(是否消失)的值在复制后保持不变(如果金箍棒有其他更多属性,也会保持不变,这里我们不过多赘述)。

如果孙悟空要复制一千根金箍棒,那么他就调用一千次自身持有的金箍棒的clone()方法即可。

通过原型模式,我们可以通过调用原型复制方法,不需要手动设置属性,就可以达到产生与原对象相同属性的对象的目的,大大简化了我们创建原型对象的工作量。

值得注意的是,原型模式是一种对象的创建模式,它并没有要求必须要通过Cloneable接口来完成,当你为一个类提供一个复制自身的方法,所有要创建相同属性的该类对象的使用者,都通过该方法来创建新对象,那么也是使用了原型模式,只是没有实现Cloneable方便安全而已。

深复制原型模式

我们都知道,孙悟空除了著名的筋斗云、火眼金睛和七十二变之外,还有很多其他的法术,比如身外身法术,就是产生一个自身的复制,下面我们来看孙悟空的深复制与浅复制的区别。

新的悟空对象,提供了浅复制与深复制两个复制方法

  1. package com.tirion.design.prototype;
  2.  
  3. public class WuKong implements Cloneable {
  4.  
  5. private GoldenCudgel goldenCudgel;
  6.  
  7. public GoldenCudgel getGoldenCudgel() {
  8. return goldenCudgel;
  9. }
  10.  
  11. public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
  12. this.goldenCudgel = goldenCudgel;
  13. }
  14.  
  15. public WuKong() {
  16. goldenCudgel = new GoldenCudgel(false);
  17. }
  18.  
  19. public WuKong clone() {
  20. WuKong wuKong = null;
  21. try {
  22. wuKong = (WuKong) super.clone();
  23. } catch (CloneNotSupportedException e) {
  24. e.printStackTrace();
  25. }
  26. return wuKong;
  27. }
  28.  
  29. public WuKong deepClone() {
  30. WuKong wuKong = null;
  31. try {
  32. wuKong = (WuKong) super.clone();
  33. wuKong.setGoldenCudgel(wuKong.getGoldenCudgel().clone());
  34. } catch (CloneNotSupportedException e) {
  35. e.printStackTrace();
  36. }
  37. return wuKong;
  38. }
  39.  
  40. public static void main(String[] args) {
  41. WuKong wuKong = new WuKong();
  42. WuKong wuKongCopy = wuKong.clone();
  43. System.out.println("浅复制后悟空是否为同一个对象" + (wuKong == wuKongCopy));
  44. System.out.println("浅复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongCopy.getGoldenCudgel()));
  45. System.out.println("浅复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  46. System.out.println("浅复制对象金箍棒属性发生改变...");
  47. wuKongCopy.getGoldenCudgel().setDisappear(true);
  48. System.out.println("浅复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  49. System.out.println("状态重置...");
  50. wuKong.getGoldenCudgel().setDisappear(false);
  51. WuKong wuKongDeepCopy = wuKong.deepClone();
  52. System.out.println("深复制后悟空是否为同一个对象" + (wuKong == wuKongDeepCopy));
  53. System.out.println("深复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
  54. System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  55. System.out.println("深复制对象金箍棒属性发生改变...");
  56. wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
  57. System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  58. }
  59.  
  60. }

执行结果:

  1. 浅复制后悟空是否为同一个对象false
  2. 浅复制后金箍棒是否为同一个对象true
  3. 浅复制原对象金箍棒属性为false
  4. 浅复制对象金箍棒属性发生改变...
  5. 浅复制原对象金箍棒属性为true
  6. 状态重置...
  7. 深复制后悟空是否为同一个对象false
  8. 深复制后金箍棒是否为同一个对象false
  9. 深复制原对象金箍棒属性为false
  10. 深复制对象金箍棒属性发生改变...
  11. 深复制原对象金箍棒属性为false

从执行结果中我们看到,浅复制后虽然悟空的复制对象与原对象不是同一个对象,但是两个悟空持有的金箍棒是同一个对象,当复制对象的金箍棒消失时,原悟空对象的金箍棒也相应消失了,这显然与我们的认知不符合,这时候,就需要深复制。

在深复制中,我们将需要深复制的属性也实现了Cloneable接口,在这里就是金箍棒类,在深复制deepClone方法中,我们不仅仅将悟空对象克隆了,同时也将需要深复制的对象克隆了一份,这样,深复制后,两个悟空持有的金箍棒就不是同一个了,复制对象的金箍棒消失,并不影响原悟空对象的金箍棒。

这里也存在一个问题,就是当对象的属性非常复杂的时候,我们的各个属性都要去实现Cloneable接口,且deepClone()方法会相当复杂。

下面我们看一下有没有更加简单的深复制方式

Java对象序列化可以将对象转化为一个字节序列,并能够通过反序列化将字节序列恢复为原来的对象,我们可以利用这一功能来实现轻量级的深复制,但前提是需要复制对象实现Serializable接口。

序列化深复制原型模式

悟空

  1. package com.tirion.design.prototype;
  2.  
  3. import java.io.ByteArrayInputStream;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.io.Serializable;
  8.  
  9. public class WuKong implements Serializable {
  10.  
  11. private GoldenCudgel goldenCudgel;
  12.  
  13. public GoldenCudgel getGoldenCudgel() {
  14. return goldenCudgel;
  15. }
  16.  
  17. public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
  18. this.goldenCudgel = goldenCudgel;
  19. }
  20.  
  21. public WuKong() {
  22. goldenCudgel = new GoldenCudgel(false);
  23. }
  24.  
  25. public WuKong deepClone() throws Exception {
  26. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  27. ObjectOutputStream oos = new ObjectOutputStream(baos);
  28. oos.writeObject(this);
  29. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  30. ObjectInputStream ois = new ObjectInputStream(bais);
  31. return (WuKong) ois.readObject();
  32. }
  33.  
  34. public static void main(String[] args) throws Exception {
  35. WuKong wuKong = new WuKong();
  36. WuKong wuKongDeepCopy = wuKong.deepClone();
  37. System.out.println("深复制后悟空是否为同一个对象" + (wuKong == wuKongDeepCopy));
  38. System.out.println("深复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
  39. System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  40. System.out.println("深复制对象金箍棒属性发生改变...");
  41. wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
  42. System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
  43. }
  44.  
  45. }

这时候运行main()方法会报java.io.NotSerializableException,因为序列化对象要求引用对象也必须实现Serializable接口,除非对应属性不需要序列化,所以我们这里需要将金箍棒类也实现序列化接口。

金箍棒

  1. package com.tirion.design.prototype;
  2.  
  3. import java.io.Serializable;
  4.  
  5. public class GoldenCudgel implements Serializable {
  6.  
  7. public GoldenCudgel() {
  8.  
  9. }
  10.  
  11. public GoldenCudgel(boolean disappear) {
  12. this.disappear = disappear;
  13. }
  14.  
  15. private boolean disappear;
  16.  
  17. public boolean isDisappear() {
  18. return disappear;
  19. }
  20.  
  21. public void setDisappear(boolean disappear) {
  22. this.disappear = disappear;
  23. }
  24.  
  25. public boolean equals(GoldenCudgel obj) {
  26. return obj.isDisappear() == disappear;
  27. }
  28.  
  29. }

执行结果

  1. 深复制后悟空是否为同一个对象false
  2. 深复制后金箍棒是否为同一个对象false
  3. 深复制原对象金箍棒属性为false
  4. 深复制对象金箍棒属性发生改变...
  5. 深复制原对象金箍棒属性为false

从结果来看,通过序列化实现深复制与通过clone()方法实现深复制的结果是一样的,但是方法却比较简单,我们只需要将需要复制的对象实现序列化接口就可以了。同时java的对象序列化是提供了轻量级持久化的,我们可以通过网络或者磁盘来进行数据的传播及持久化,并且就突破了clone()方法只能本地程序运行期间才能持久化的限制。

关于原型模式的介绍就到这里,你可以将它记忆为身外身模式

如果你认为文章中哪里有错误或者不足的地方,欢迎在评论区指出,也希望这篇文章对你学习java设计模式能够有所帮助。转载请注明,谢谢。

更多设计模式的介绍请到悟空模式-java设计模式中查看。

悟空模式-java-原型模式的更多相关文章

  1. Java进阶篇设计模式之三 ----- 建造者模式和原型模式

    前言 在上一篇中我们学习了工厂模式,介绍了简单工厂模式.工厂方法和抽象工厂模式.本篇则介绍设计模式中属于创建型模式的建造者模式和原型模式. 建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用 ...

  2. Java原型模式

    原型模式 原型模式也称克隆模式.原型模式jian ming zhi yi,就是先创造出一个原型,然后通过类似于Java中的clone方法,对对象的拷贝,克隆类似于new,但是不同于new.new创造出 ...

  3. Java设计模式之三 ----- 建造者模式和原型模式

    前言 在上一篇中我们学习了工厂模式,介绍了简单工厂模式.工厂方法和抽象工厂模式.本篇则介绍设计模式中属于创建型模式的建造者模式和原型模式. 建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用 ...

  4. Java 原型模式(克隆模式)

      Java 的设计模式有 23 种,前段时间小编已经介绍了单例模式,由于我们在学习 Spring 的时候在 bean 标签的学习中碰到了今天要讲的原型模式,那么小编就已本文来介绍下原型模式. 原型模 ...

  5. 建造者模式与原型模式/builder模式与prototype模式/创建型模式

    建造者模式 定义 用于简化复杂对象的创建 JDK中的建造者模式 java.lang.StringBuilder中的append()方法,每次调用后返回修改后的对象本身. public StringBu ...

  6. 设计模式10---设计模式之原型模式(Prototype)

    1.场景模式 考虑这样一个实际应用:订单处理系统 里面有一个保存订单的功能,当产品数量超过1000份以后,拆成两份订单,再超,那么就再拆.直到每份订单不超过1000为止,订单有两种,一个是个人订单,一 ...

  7. [19/04/24-星期三] GOF23_创建型模式(建造者模式、原型模式)

    一.建造者模式 本质:分离了对象子组件的单独构造(由Builder负责)和装配的分离(由Director负责),从而可以构建出复杂的对象,这个模式适用于:某个对象的构建过程十分复杂 好处:由于构建和装 ...

  8. 初涉JavaScript模式 (7) : 原型模式 【三】

    组合使用构造函数模式和原型模式 上篇,我们提到了原型模式的缺点,就是每个实例不能拥有自己的属性,因为纯原型模式所有的属性都是公开给每个实例的,故我们可以组合使用构造函数模式和原型模式.构造函数用来定义 ...

  9. 初涉JavaScript模式 (5) : 原型模式 【一】

    什么是原型模式? 原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象.--引自JavaScript设计模式 我们创建的每一个函数都有一个prototype ...

  10. JavaScript之面向对象学习六原型模式创建对象的问题,组合使用构造函数模式和原型模式创建对象

    一.仔细分析前面的原型模式创建对象的方法,发现原型模式创建对象,也存在一些问题,如下: 1.它省略了为构造函数传递初始化参数这个环节,结果所有实例在默认的情况下都将取得相同的属性值,这还不是最大的问题 ...

随机推荐

  1. CE修改器使用教程 [基础篇]

    Cheat Engine  是一款内存修改编辑工具 ,它允许你修改你的游戏或软件内存数据,以得到一些其他功能.它包括16进制编辑,反汇编程序,内存查找工具.与同类修改工具相比,它具有强大的反汇编功能, ...

  2. 货车运输(最大生成树+倍增LCA)

    看到第一篇题解的神奇码风--我决定发一篇码风正常的题解造福人类 这题的做法也非常经典,最大生成树\(+LCA\),相当于先贪心一下,在LCA的时候记录一下当前最小的边权 顺便吐槽一下最后一个测试点: ...

  3. dubbo-admin 出现警告(不影响使用)

    <dubbo:application name="pyg-sellergoods-s" />. <dubbo:application name="pyg ...

  4. Fusioncharts的数字格式化

      1.     小数点位数格式化 <chart ... decimals='2' > Eg.数值12.432, 13.4 and 13,使用<chart ... decimals= ...

  5. HTML元素ID和JS方法名重复,JS调用失败

    HTML元素ID和JS方法名重复时,JS中的重名方法无法被找到,不能执行. 修改ID或者方法名,两者不一致即可.

  6. day 38 jq 入门 学习(一)

    前情提要: jq是简化版本的js 可以把很多很复杂的js 提炼让前端代码更好写 一:jq的使用 <!DOCTYPE html> <html lang="en"&g ...

  7. 【并发】3、LockSupport阻塞与唤醒,相较与wait和notify

    我们可以使用wait和notify分别对象线程进行阻塞或者唤醒,但是我们也可以使用LockSupport实现一样的功能,并且在实际使用的时候,个人感觉LockSupport会更加顺手 范例1,wait ...

  8. numpy中int类型与python中的int

    [code] import numpy as np nparr = np.array([[1 ,2, 3, 4]]) np_int32 = nparr[0][0] # np_int=1 py_int ...

  9. Firefox火狐 浏览器接口调试工具 JSON 格式化

    作为一名“IT界”的淫才,还是主攻Web端的淫才,相信大家经常会联调各种接口! 如今APP猖狂的年代接口联调更为频繁,当然我们常用于Firefox火狐 浏览器,所以这里主要讲Firefox火狐 浏览器 ...

  10. logstash笔记(一)——redis&es

    下载地址: https://www.elastic.co/downloads 版本:logstash-2.2.2 两台linux虚拟机,一台windows宿主机 shipper: 192.168.22 ...