概论
什么是原型模式呢?用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。这个模式就叫作原型模式。原型模式属于对象创建者模式。
原型模式示例
首先我们需要有一个原型。这个原型实现了Cloneable空接口。这是一个标记接口,并无任何的方法。
1 package com.example.pattern.prototype; 2 3 import lombok.*; 4 5 import java.io.Serializable; 6 import java.util.Arrays; 7 import java.util.List; 8 9 @Setter10 @Getter11 @NoArgsConstructor12 @AllArgsConstructor13 public class PrototypeClass implements Cloneable {14 15 private int id;16 private char code;17 private String name;18 19 private BaseDomain baseDomain;20 private String[] array;21 22 private Listlist;23 24 25 @Override26 protected PrototypeClass clone() {27 PrototypeClass prototypeClass = null;28 29 try {30 prototypeClass = (PrototypeClass) super.clone();31 } catch (CloneNotSupportedException e) {32 e.printStackTrace();33 34 }35 36 return prototypeClass;37 }38 39 40 }
第9行-12行:采用lambok注解,简化了简单对象中的繁琐的get,set,带所有参数的构造函数,无参构造函数。
第15-22行:定义了类型为原子类型,复杂对象,已经数组和集合的成员属性。
最后,我们需要增加一个场景类Client:
1 public class Client { 2 3 public static void main(String[] args) { 4 PrototypeClass prototypeClass = new PrototypeClass(); 5 prototypeClass.setId(123); 6 prototypeClass.setCode('A'); 7 prototypeClass.setName("Tom"); 8 prototypeClass.setBaseDomain(new BaseDomain()); 9 10 String[] array = new String[]{"22222"};11 prototypeClass.setArray(array);12 13 Listlist = new ArrayList ();14 list.add("CCC");15 prototypeClass.setList(list);16 17 PrototypeClass cloneClass = prototypeClass.clone();18 19 cloneClass.setId(456);20 cloneClass.setCode('B');21 cloneClass.setName("Jack");22 23 System.out.println("prototypeClass.getId() :"+prototypeClass.getId());24 System.out.println("cloneClass.getId():"+cloneClass.getId());25 26 System.out.println("prototypeClass.getCode() :"+prototypeClass.getCode());27 System.out.println("cloneClass.getCode():"+cloneClass.getCode());28 29 System.out.println("prototypeClass.getName() :"+prototypeClass.getName());30 System.out.println("cloneClass.getName():"+cloneClass.getName());31 32 String[] array2 = new String[]{"33333"};33 cloneClass.setArray(array);34 35 List list2 = new ArrayList ();36 list.add("DDDD");37 cloneClass.setList(list);38 39 40 System.out.println("prototypeClass.getList().get(0) :"+prototypeClass.getList().get(0));41 System.out.println("cloneClass.getList().get(0) :"+cloneClass.getList().get(0));42 43 System.out.println("prototypeClass.getArray()[0] :"+prototypeClass.getArray()[0]);44 System.out.println("cloneClass.getArray()[0] :"+cloneClass.getArray()[0]);45 46 47 48 }49 }
第17行:调用了对象的clone方法,直接产生一个对象。这就是对象的复制,而不是使用new 指令。使用对象复制的方式,不会调用构造函数。
我们先执行一下打印出来的结果:
1 prototypeClass.getId() :123 2 cloneClass.getId():456 3 prototypeClass.getCode() :A 4 cloneClass.getCode():B 5 prototypeClass.getName() :Tom 6 cloneClass.getName():Jack 7 prototypeClass.getList().get(0) :CCC 8 cloneClass.getList().get(0) :CCC 9 prototypeClass.getArray()[0] :2222210 cloneClass.getArray()[0] :22222
从以上执行结果来看
①:如果成员属性为int char String类型,复制后的对象的属性的改变不会对原始对象的属性产生任何的影响。
②:如果成员属为是List集合,数组,复制后的对象的属性的改变也会和原始对象的属性产生了影响。
这是为什么呢?因为我们在原型中的拷贝方式是浅拷贝。什么是浅拷贝呢?super.clone是谁的方法呢,当然是Object方法的,因为Object是任何类的超类。而Object类提供的clone方法只是拷贝本对象,这个对象的内部成员属性包括数组,集合,引用对象都不拷贝,还是执行原生对象的内部元素地址。因此数组,集合,引用对象都是在原生对象还是拷贝对象中都是共享而存在的。这就是浅拷贝。
浅拷贝的对象中的成员属性还有对象的情况下, 像以上例子中的 BaseDomain。改变了同一个BaseDomian实例的属性name的情况下, 因为是同一个实例,因此也是共享的,一边都变。如果是重新new一个BaseDomain实例,重新对拷贝之后的对象复制,那是互相不干扰的。
原型模式在源码中的应用
1 public Object clone() { 2 try { 3 ArrayList v = (ArrayList ) super.clone(); 4 v.elementData = Arrays.copyOf(elementData, size); 5 v.modCount = 0; 6 return v; 7 } catch (CloneNotSupportedException e) { 8 // this shouldn't happen, since we are Cloneable 9 throw new InternalError(e);10 }11 }
以上是ArrayList的clone方法,我们可以产生了一个list之后,来clone一下,来简化操作,这里用到的是深拷贝。为什么是深拷贝而不是浅拷贝呢?第4-行-5行数组,修改次数再复制,拷贝之后的对象与原来的对象不再持有同一份引用,因此是深拷贝。而且Arrays.copyOf方法中是重新创建的一个新数组。