Java设计模式之原型模式
概论
什么是原型模式呢?用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。这个模式就叫作原型模式。原型模式属于对象创建者模式。
原型模式示例
首先我们需要有一个原型。这个原型实现了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 @Setter 10 @Getter 11 @NoArgsConstructor 12 @AllArgsConstructor 13 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 List<String> list; 23 24 25 @Override 26 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 List<String> list = new ArrayList<String>(); 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<String> list2 = new ArrayList<String>(); 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] :22222 10 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方法中是重新创建的一个新数组。