设计模式 原型模式

words: 1.1k    views:    time: 5min

通过给定的原型对象,获取所要创建的对象类型,然后复制这个原型对象创建出更多同类型的对象。

1. 结构

  • 客户角色(Client):客户类提出创建对象的请求。
  • 抽象原型角色(Prototype):给出具体原型类需要的所有接口。
  • 具体原型角色(ConcretePrototype):被复制的对象。

原型模式的关键在于如何实现克隆方法。

2. 应用

2.1. java clone()

1
2
3
4
5
6
public class Object {

protected native Object clone() throws CloneNotSupportedException;

//...
}

java中,Object类提供一个clone()方法,可以将给定的对象克隆一份。需要注意的是这个类必须实现标识接口Cloneable,表示它可以被复制,否则将抛出一个CloneNotSupportedException异常。

3. 对象复制

  • 浅复制:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
  • 深复制:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用指向被复制过的新对象,但是深复制深入到多少层,是一个不易确定的问题。

3.1. java 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
32
33
34
35
36
37
public class Bean implements Cloneable {

public int value;

public String name;

public ArrayList<Bean> list;

public Bean(int value, String name, ArrayList<Bean> list){
this.value = value;
this.name = name;
this.list = list;
}

@Override
public Object clone() throws CloneNotSupportedException{
return super.clone();
}

public static void main(String[] args) throws Exception {
ArrayList<Bean> list = new ArrayList<>();
list.add(new Bean(11, "c1", null));
list.add(new Bean(12, "c2", null));

Bean bean1 = new Bean(1, "name1", list);

Bean bean2 = (Bean)bean1.clone();
bean2.value = 2;
bean2.name = "name2";
bean2.list.add(new Bean(13, "c3", null));

System.out.println(bean1);
System.out.println(bean1.value + ", " + bean1.name + ", " + bean1.list);
System.out.println(bean2);
System.out.println(bean2.value + ", " + bean2.name + ", " + bean2.list);
}
}
1
2
3
4
demo.Bean@15db9742
1, name1, [demo.Bean@6d06d69c, demo.Bean@7852e922, demo.Bean@4e25154f]
demo.Bean@70dea4e
2, name2, [demo.Bean@6d06d69c, demo.Bean@7852e922, demo.Bean@4e25154f]

可以看出bean1bean2中的list其实是同一个,可以重写clone(),将需要复制的引用对象也进行复制,这样就可以实现更深一层的复制了,但是引用对象中可能还会再有引用对象,它们并不会被复制,比如:

1
2
3
4
5
public Object clone() throws CloneNotSupportedException{
Bean bean = (Bean)super.clone();
bean.list = (ArrayList<Bean>) list.clone();
return bean;
}
1
2
3
4
demo.Bean@15db9742
1, name1, [demo.Bean@6d06d69c, demo.Bean@7852e922]
demo.Bean@4e25154f
2, name2, [demo.Bean@6d06d69c, demo.Bean@7852e922, demo.Bean@70dea4e]

可以看出,虽然两个Bean中的list已经不是同一个,但是复制之后list中的Bean对象其实是一样的。

这个可以看下ArrayList中的clone()实现,它其实就是新建了一个实例,然后将之前数组中的元素拷贝了一份,因此上面Bean中的list在复制是只是换了个壳子,内容没有变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayList<E> extends AbstractList<E> implements List<E>, 
RandomAccess, Cloneable, java.io.Serializable {

//...

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);
}
}
}

3.2. 序列化

如果要实现深复制,可以利用序列化:将对象写到流里,然后再把对象从流里读出来即实现一个对象的拷贝,而原来对象仍然存在于JVM中。

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
public class Bean implements Serializable {

private static final long serialVersionUID = 8421891173574765485L;

public int value;

public String name;

public ArrayList<Bean> list;

public Bean(int value, String name, ArrayList<Bean> list){
this.value = value;
this.name = name;
this.list = list;
}

public static void main(String[] args) throws Exception {
ArrayList<Bean> list = new ArrayList<>();
list.add(new Bean(11, "c1", null));
list.add(new Bean(12, "c2", null));

Bean bean1 = new Bean(1, "name1", list);

//将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(bean1);

//将对象从流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Bean bean2 = (Bean)ois.readObject();

bean2.value = 2;
bean2.name = "name2";
bean2.list.add(new Bean(13, "c3", null));

System.out.println(bean1);
System.out.println(bean1.value + ", " + bean1.name + ", " + bean1.list);

Bean bean = bean1.list.get(0);
System.out.println(bean.value + ", " + bean.name + ", " + bean.list);

System.out.println(bean2);
System.out.println(bean2.value + ", " + bean2.name + ", " + bean2.list);

bean = bean2.list.get(0);
System.out.println(bean.value + ", " + bean.name + ", " + bean.list);
}
}
1
2
3
4
5
6
demo.Bean@55f96302
1, name1, [demo.Bean@7d4991ad, demo.Bean@74a14482]
11, c1, null
demo.Bean@14ae5a5
2, name2, [demo.Bean@7f31245a, demo.Bean@6d6f6e28, demo.Bean@135fbaa4]
11, c1, null

可以看出,通过序列化不仅将Bean中的list复制了,而且将list中的引用对象也进行复制了。


参考:

  1. 《java与模式》