通过给定的原型对象,获取所要创建的对象类型,然后复制这个原型对象创建出更多同类型的对象。
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]
|
可以看出bean1
和bean2
中的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) { 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中的引用对象也进行复制了。
参考:
- 《java与模式》