来源:https://blog.csdn.net/ayunlong/article/details/130298135
Java设计模式-原型模式(Prototype Pattern)
原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。
一、什么是原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。原型实例指定了要创建对象的种类,创建时无需知道对象创建细节。
原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
这种形式涉及到三个角色:
- 客户(Client)角色:客户类提出创建对象的请求。
- 抽象原型(Prototype)角色:抽象角色,通常由一个Java接口或Java抽象类实现,此角色给出所有的具体原型类所需的接口(如Object的clone方法)。
- 具体原型(Concrete Prototype)角色:被复制的对象,实现复制接口
二、原型模式的2种实现方式
自定义克隆方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package org.PrototypePattern;
interface Sheep{ public DuoLiSheep clone(); }
class DuoLiSheep implements Sheep{ private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public DuoLiSheep clone() { DuoLiSheep duoLiSheep = new DuoLiSheep(); duoLiSheep.setName(this.getName()); return duoLiSheep; } }
public class Custom { public static void main(String[] args) { DuoLiSheep duoLiSheep = new DuoLiSheep(); duoLiSheep.setName("多利"); DuoLiSheep duoLiSheep1 = new DuoLiSheep(); duoLiSheep1.setName(duoLiSheep.getName());
DuoLiSheep duoLiSheep2 = duoLiSheep.clone();
System.out.println("多利1号与多利2号是同一个对象吗? " + (duoLiSheep.equals(duoLiSheep2) ?"是":"不是")); } }
多利1号与多利2号是同一个对象吗? 不是
|
上面的代码中写了普通克隆方案和原型模式的克隆方法,可以总结出
- 原型模式操作简单,屏蔽对象创建细节
- 普通的克隆方法需要获取原始对象的属性,一一赋值给新对象,过于复杂
Object类的clone方法
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:
- 对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
- 对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。
- 如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
在JAVA语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。JAVA语言的设计师在设计自己的clone()方法时,也应当遵守着三个条件。一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package org.PrototypePattern.version2;
class DuoLiSheep implements Cloneable{ private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public Object clone() throws CloneNotSupportedException { System.out.println("多利羊复制成功!"); return (DuoLiSheep) super.clone(); } } public class JDKClone { public static void main(String[] args) throws CloneNotSupportedException { DuoLiSheep duoLiSheep = new DuoLiSheep(); duoLiSheep.setName("多利"); DuoLiSheep duoLiSheep1 = (DuoLiSheep) duoLiSheep.clone(); System.out.println("两个对象是否相同?" + (duoLiSheep1.equals(duoLiSheep)));
} }
多利羊复制成功! 两个对象是否相同?false
|
优点
Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
需要为每一个类都配置一个clone方法
clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
浅拷贝是什么?
数据类型是基本数据类型或String,浅拷贝直接进行值传递,即将属性值直接复制一份给新的对象。
数据类型是引用类型(对象、数组),浅拷贝会进行引用传递,也就是将成员变量的引用值(内存地址)复制一份给新的对象。如属性A是一个引用类型,那么浅拷贝后newObject.A=oldObject.A。如果A有修改,将影响到newObject和oldObject。
上面用原型模式改进的克隆羊的例子就是浅拷贝,浅拷贝是用默认的clone()来实现的
深拷贝是什么?
相对于浅拷贝来说,深拷贝对基本类型或String类型的处理是一样的,不同的是处理引用类型。深拷贝会为所有的引用类型的成员变量申请存储空间,并复制每个引用数据成员变量所引用的对象,直到改对象可达的所有对象。 深拷贝的两种实现方式
方式1:重写clone方法来实现深度克隆
方式2:利用序列化实现深度克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package org.PrototypePattern.version4;
import java.io.*; import java.util.ArrayList; import java.util.List;
class DuoLiSheep implements Cloneable, Serializable { private String name; private List child = new ArrayList();
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List getChild() { return child; }
public void setChild(List child) { this.child = child; }
@Override public Object clone() throws CloneNotSupportedException { ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); DuoLiSheep copyObj = (DuoLiSheep) ois.readObject();
return copyObj; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (Exception e2) { e2.printStackTrace(); } } } } public class DeepCopy { public static void main(String[] args) throws CloneNotSupportedException { DuoLiSheep duoLiSheep = new DuoLiSheep(); duoLiSheep.setName("多利"); DuoLiSheep duoLiSheep1 = (DuoLiSheep) duoLiSheep.clone(); System.out.println("两个对象是否相同?" + (duoLiSheep1.equals(duoLiSheep))); } }
两个对象是否相同?false
|
反序列化可以实现对象的深拷贝,其实就是创建了新的对象,在单例模式中我们提到过反序列化可以破坏单例模式,就是因为反序列化创建的是新的对象,而且属性与旧对象完全相同,无需区别对象的属性是简单类型还是负责类型。
- 原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。