通过给定的原型对象,获取所要创建的对象类型,然后复制这个原型对象创建出更多同类型的对象。
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与模式》