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

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

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

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

java.lang.Cloneable

/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}

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

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

/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
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;

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

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

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

浅复制原型模式

package com.tirion.design.prototype;

public class GoldenCudgel implements Cloneable {

    public GoldenCudgel() {

    }

    public GoldenCudgel(boolean disappear) {
this.disappear = disappear;
} private boolean disappear; public boolean isDisappear() {
return disappear;
} public void setDisappear(boolean disappear) {
this.disappear = disappear;
} public GoldenCudgel clone() {
GoldenCudgel goldenCudgel = null;
try {
goldenCudgel = (GoldenCudgel) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return goldenCudgel;
} public boolean equals(GoldenCudgel obj) {
return obj.isDisappear() == disappear;
} }

悟空

package com.tirion.design.prototype;

public class WuKong {

    private static GoldenCudgel goldenCudgel = new GoldenCudgel(false);

    public static void main(String[] args) {
GoldenCudgel copyGoldenCudgel = goldenCudgel.clone();
System.out.println(goldenCudgel);
System.out.println(copyGoldenCudgel);
System.out.println(goldenCudgel != copyGoldenCudgel);
System.out.println(goldenCudgel.getClass() == copyGoldenCudgel.getClass());
System.out.println(goldenCudgel.equals(copyGoldenCudgel));
} }

打印结果

com.tirion.design.prototype.GoldenCudgel@74a14482
com.tirion.design.prototype.GoldenCudgel@1540e19d
true
true
true

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

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

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

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

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

深复制原型模式

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

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

package com.tirion.design.prototype;

public class WuKong implements Cloneable {

    private GoldenCudgel goldenCudgel;

    public GoldenCudgel getGoldenCudgel() {
return goldenCudgel;
} public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
this.goldenCudgel = goldenCudgel;
} public WuKong() {
goldenCudgel = new GoldenCudgel(false);
} public WuKong clone() {
WuKong wuKong = null;
try {
wuKong = (WuKong) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return wuKong;
} public WuKong deepClone() {
WuKong wuKong = null;
try {
wuKong = (WuKong) super.clone();
wuKong.setGoldenCudgel(wuKong.getGoldenCudgel().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return wuKong;
} public static void main(String[] args) {
WuKong wuKong = new WuKong();
WuKong wuKongCopy = wuKong.clone();
System.out.println("浅复制后悟空是否为同一个对象" + (wuKong == wuKongCopy));
System.out.println("浅复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongCopy.getGoldenCudgel()));
System.out.println("浅复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
System.out.println("浅复制对象金箍棒属性发生改变...");
wuKongCopy.getGoldenCudgel().setDisappear(true);
System.out.println("浅复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
System.out.println("状态重置...");
wuKong.getGoldenCudgel().setDisappear(false);
WuKong wuKongDeepCopy = wuKong.deepClone();
System.out.println("深复制后悟空是否为同一个对象" + (wuKong == wuKongDeepCopy));
System.out.println("深复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
System.out.println("深复制对象金箍棒属性发生改变...");
wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
} }

执行结果:

浅复制后悟空是否为同一个对象false
浅复制后金箍棒是否为同一个对象true
浅复制原对象金箍棒属性为false
浅复制对象金箍棒属性发生改变...
浅复制原对象金箍棒属性为true
状态重置...
深复制后悟空是否为同一个对象false
深复制后金箍棒是否为同一个对象false
深复制原对象金箍棒属性为false
深复制对象金箍棒属性发生改变...
深复制原对象金箍棒属性为false

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

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

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

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

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

序列化深复制原型模式

悟空

package com.tirion.design.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class WuKong implements Serializable { private GoldenCudgel goldenCudgel; public GoldenCudgel getGoldenCudgel() {
return goldenCudgel;
} public void setGoldenCudgel(GoldenCudgel goldenCudgel) {
this.goldenCudgel = goldenCudgel;
} public WuKong() {
goldenCudgel = new GoldenCudgel(false);
} public WuKong deepClone() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (WuKong) ois.readObject();
} public static void main(String[] args) throws Exception {
WuKong wuKong = new WuKong();
WuKong wuKongDeepCopy = wuKong.deepClone();
System.out.println("深复制后悟空是否为同一个对象" + (wuKong == wuKongDeepCopy));
System.out.println("深复制后金箍棒是否为同一个对象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel()));
System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
System.out.println("深复制对象金箍棒属性发生改变...");
wuKongDeepCopy.getGoldenCudgel().setDisappear(true);
System.out.println("深复制原对象金箍棒属性为" + wuKong.getGoldenCudgel().isDisappear());
} }

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

金箍棒

package com.tirion.design.prototype;

import java.io.Serializable;

public class GoldenCudgel implements Serializable {

    public GoldenCudgel() {

    }

    public GoldenCudgel(boolean disappear) {
this.disappear = disappear;
} private boolean disappear; public boolean isDisappear() {
return disappear;
} public void setDisappear(boolean disappear) {
this.disappear = disappear;
} public boolean equals(GoldenCudgel obj) {
return obj.isDisappear() == disappear;
} }

执行结果

深复制后悟空是否为同一个对象false
深复制后金箍棒是否为同一个对象false
深复制原对象金箍棒属性为false
深复制对象金箍棒属性发生改变...
深复制原对象金箍棒属性为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. Flask系列09--Flask中WTForms插件,及自定义验证器

    一.概述 django中的forms组件非常的方便,在flask中有WTForms的组件实现的也是类似的功能, 安装这个插件 二.简单使用 文档地址https://wtforms.readthedoc ...

  2. 在Node中使用ES6语法

    Node本身已经支持部分ES6语法,但是import export,以及async await(Node 8 已经支持)等一些语法,我们还是无法使用.为了能使用这些新特性,我们就需要使用babel把E ...

  3. [学习笔记]后缀自动机SAM

    好抽象啊,早上看了两个多小时才看懂,\(\%\%\%Fading\) 早就懂了 讲解就算了吧--可以去看看其他人的博客 1.[模板]后缀自动机 \(siz\) 为该串出现的次数,\(l\) 为子串长度 ...

  4. nodejs&mongo&angularjs

    http://www.ibm.com/developerworks/cn/web/wa-nodejs-polling-app/

  5. 浏览器内核、渲染引擎、JS引擎简介

    一.定义 浏览器内核分成两部分:渲染引擎和JS引擎. 由于JS引擎越来越独立,浏览器内核 就倾向于 单指 渲染引擎.  渲染引擎是一种对HTML文档进行解析并将其显示在页面上的工具.(说白了,就是按照 ...

  6. Java实现二叉树先序,中序,后序,层次遍历

    一.以下是我要解析的一个二叉树的模型形状.本文实现了以下方式的遍历: 1.用递归的方法实现了前序.中序.后序的遍历: 2.利用队列的方法实现层次遍历: 3.用堆栈的方法实现前序.中序.后序的遍历. . ...

  7. MVC3学习:利用mvc3+ajax结合MVCPager实现分页

    本例使用表格Users(Uid,UserName,PassWord),数据库访问使用EF first code. public class Users { [Key] public int Uid { ...

  8. PHP的语言构造器

    isset和empty看起来像是函数,我们也经常把它当作函数一样使用,但是实际上,它们是语言构造器. php中的语言构造器就相当于C中的预定义宏的意思,它属于php语言内部定义的关键词,不可以被修改, ...

  9. Proxy代理模式(结构型模式)

    1.问题 在面向对象系统中,有些对象由于某种原因(比如创建对象的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给调用者带来麻烦,那么如何在不损失接口透明性的情况下,解决这些麻 ...

  10. [转]网页实时聊天之js和jQuery实现ajax长轮询 PHP

    网页实时聊天之js和jQuery实现ajax长轮询 众所周知,HTTP协议是无状态的,所以一次的请求都是一个单独的事件,和前后都没有联系.所以我们在解决网页实时聊天时就遇到一个问题,如何保证与服务器的 ...