原型模式



关于克隆,我们会想到《西游记》里面孙悟空的猴毛分身,也会想到《火影忍者》里面的影分身之术。他们都是用一个物体复制若干个一模一样的物体。在面向对象的系统中,我们也可以通过克隆来复制一些对象—也就是我们所说的原型模式

用原型实例指定创建对象的种类,通过拷贝这些原型创建新的对象,也就是利用一个原型对象来指明我们要创建对象的类型,然后通过复制这个对象来获取一模一样的对象实例

一个简单的原型模式Demo

1
2
3
4
5
6
7
8
9
10
11
12
public class PrototypeClass implements Cloneable{
@Override
protected PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
}catch (CloneNotSupportedException e){

}
return prototypeClass;
}
}
  • 实现Cloneable接口,在JVM中只有具有这个标记的对象才有可能被拷贝
  • 重写覆盖Clone()方法

为什么要用原型模式

  • 性能优良。 原型模式是在内存二进制流的拷贝,比直接new一个对象性能要好得多。所以特别是要在一个循环体内产生大量对象的时候,原型模式更好体现其优点
  • 避开构造函数的束缚(既是优点也是缺点)
    直接在内存中拷贝,构造函数是不会执行的

原型模式应用场景

  1. 资源优化场景
  2. 性能和安全要求场景
  3. 一个对象多个修改者的场景

两种拷贝方式

浅拷贝

只拷贝本对象,对象内部的数组、引用对象都不拷贝,还是指向原生对象的内部元素地址。原始类型(int、long、char)以及String都会被拷贝

如何才能保证成员变量不被拷贝(保证以下两个条件)
  • 必须是类的成员变量,而不是方法内变量
  • 必须是一个可变的引用对象,而不是一个原始类型或者不可变对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Thing implements Cloneable {
private List<String> list = new ArrayList<String>();

@Override
protected Thing clone(){
Thing thing = null;
try {
thing = (Thing)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return thing;
}

public void setValue(String value){
this.list.add(value);
}

public List<String> getValue(){
return this.list;
}
}

浅克隆是不安全的方式,两个对象共享了一个私有变量,大家都能够进行修改。

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DeepThing implements Cloneable {
private ArrayList<String> list = new ArrayList<String>();

@Override
protected DeepThing clone(){
DeepThing thing = null;
try {
thing = (DeepThing)super.clone();
this.list = (ArrayList<String>)this.list.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return thing;
}

public void setValue(String value){
this.list.add(value);
}

public List<String> getValue(){
return this.list;
}
}

总结

  • 原型模式向客户隐藏了创建对象的复杂性,客户只需要知道创建对象类型,就可以获得对象一模一样的新对象。
  • 有两种克隆方式:深克隆、浅克隆
    • 浅克隆;不安全,对象公有私有变量
    • 有时对象的复制可能会比较复杂