设计模式之原型模式
prototype design pattern
原型模式的概念、原型模式的结构、原型模式的优缺点、原型模式的使用场景、浅拷贝与深拷贝、原型模式的实现示例、原型模式的源码分析
1、原型模式的概念
原型模式,即将一个对象作为原型对象,通过对其克隆而复制出多个与原型对象类似的新实例。
使用原型模式创建对象要比使用 new 等关键字创建对象性能好的多,因为原型模式的本质是使用 Object 的 clone() 方法克隆对象,而这个方法是本地方法,它直接操作内存中的二进制流,特别是大对象时,性能差异特别明显。使用原型模式就好比复制粘贴,而不是亲手码。需要注意的是,使用原型模式时需要考虑克隆的深、浅对结果的影响。
2、原型模式的结构
- 原型类实现 Cloneable 接口。
- 原型类实现 clone() 方法。
3、原型模式的优缺点
3.1、优点
- 比使用 new 等关键字创建对象在性能上好很多,因为其是本地方法,直接操作内存中的二进制流。
- 对于相似度高的大对象的创建,效率要高很多。
- 原型对象在继承时,子类重写父类方法时可先调用父类方法,在扩展,开发效率高。
3.2、缺点
- 当多个目标对象共享同一个原型对象的引用数据时(如 array、object 等),可能会相互影响。主要原因是浅拷贝与深拷贝的实现不同。
4、原型模式的使用场景
- 基类的结构和数据都要被复用时。
- 对目标对象的修改不影响既有的原型对象时(深拷贝时完全不影响)。
- 当一个对象创建时需要繁琐的数据准备和访问权限时。
- 当一个对象需要共享给多个对象,且每个对象都有可能修改该对象时。
- 原型模式很少单独出现,一般和工厂方法模式和抽象工厂模式结合使用,先通过原型模式创建一个对象,然后由工厂方法或抽象工厂提供给调用者使用。
5、深拷贝与浅拷贝
-
浅拷贝:
浅拷贝只是在栈上多了一份当前对象的引用,当前对象在堆中只有一份,且地址未发生变化。
-
深拷贝:
深拷贝是将当前对象复制了一份,且在堆中分配了内存,从物理层面来说,拷贝结果与原始对象已经是两个东西了,只是长得一样而已。
6、原型模式的实现示例
6.1、浅拷贝实现原型模式
定义原型类:
public class Prototype implements Cloneable {
private String username;
private Integer gender;
private Prototype friend;
public Prototype(String username, Integer gender) {
super();
this.username = username;
this.gender = gender;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Prototype getFriend() {
return friend;
}
public void setFriend(Prototype friend) {
this.friend = friend;
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
测试类:
public class PrototypeTest {
public static void main(String[] args) {
Prototype prototype = new Prototype("zed", 24);
prototype.setFriend(new Prototype("fizz", 21));
Prototype prototype1 = prototype.clone();
Prototype prototype2 = prototype.clone();
System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getFriend().hashCode());
System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getFriend().hashCode());
System.out.println("prototype1 = " + prototype1 + " prototype1.friend = "+ prototype1.getFriend().hashCode());
System.out.println("prototype2 = " + prototype2 + " prototype2.friend = " + prototype2.getFriend().hashCode());
}
}
测试结果:
可以看到,浅拷贝只是复制了当前对象的指针,也就是说只是在栈上多了一份原对象的引用。
prototype = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@5034c75a prototype.friend = 748658608
prototype1 = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@396a51ab prototype1.friend = 748658608
prototype2 = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@51081592 prototype2.friend = 748658608
6.2、深拷贝实现原型模式
引用对象:
public class Children implements Cloneable, Serializable {
private static final long serialVersionUID = 2263495181008794106L;
private String username;
private Integer gender;
public Children(String username, Integer gender) {
this.username = username;
this.gender = gender;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
@Override
public Children clone() {
try {
// 该类属性全是非引用数据类型 故直接使用默认 clone 实现
return (Children) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
原型类:
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = -2375259498572341431L;
private String username;
private Children children;
public Prototype(String username, Children children) {
super();
this.username = username;
this.children = children;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Children getChildren() {
return children;
}
public void setChildren(Children children) {
this.children = children;
}
/**
* 深拷贝
* 深拷贝推荐用序列化实现
* @return
*/
@Override
public Prototype clone() {
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
// 序列化
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
// 反序列化
byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
return (Prototype) o;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
if (byteArrayInputStream != null) {
byteArrayInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
测试类:
public class PrototypeTest {
public static void main(String[] args) {
Children children = new Children("Kaisa", 21);
Prototype prototype = new Prototype("Kassadin", children);
Prototype prototype1 = prototype.clone();
Prototype prototype2 = prototype.clone();
System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getChildren().hashCode());
System.out.println("prototype1 = " + prototype1 + " prototype1.friend = "+ prototype1.getChildren().hashCode());
System.out.println("prototype2 = " + prototype2 + " prototype2.friend = " + prototype2.getChildren().hashCode());
}
}
测试结果:
可以看到,其引用对象的地址发生了变化,说明深拷贝是将原对象复制了一份,且在堆中重新分配了内存。
prototype = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@50c87b21 prototype.friend = 403716510
prototype1 = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@5649fd9b prototype1.friend = 701141022
prototype2 = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@2d928643 prototype2.friend = 112061925
7、原型模式的源码分析
原型模式的本质是拷贝原来的对象从而创建新的对象。但当原对象存在引用数据类型时,若使用浅拷贝实现,则对新对象的修改会导致原对象也被修改;若使用深拷贝实现,则不会出现这种情况。
JDK 中存在很多深拷贝的源码,以 java.util.ArrayList 为例:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
// 在这里对数组进行了拷贝 这个拷贝实际上是在堆中生成了一个新对象
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}