原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
使用场景:
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
简单的说,原型模式就是复制一个原有的对象,在Java中复制对象的方法有:
1、手动将实例A的值赋值给实例B
1、通过重写java.lang.Object类中的方法clone();
2、通过序列化、反序列化实现对象的复制。
1、重写java.lang.Object类中的方法clone():
Java中克隆分为深克隆和浅克隆
浅克隆:克隆对象时仅仅拷贝对象本身(基本数据类型和String),而不克隆引用的其他对象
深克隆:不仅克隆对象本身,而且克隆对象包含的引用指向的所有对象。
Cast1:浅克隆
准备猴类和猪类:因为要克隆猴类,所以要实现Cloneable接口,重写Clone()方法

1 // 实现 Cloneable 接口 2 class Monkey implements Cloneable { 3 public String name; 4 public int age; 5 Pig friend; 6 7 public Monkey(String name, int age) { 8 this.name = name; 9 this.age = age; 10 } 11 12 //重写 clone() 方法 13 @Override 14 public Object clone() { 15 Monkey monkey = null; 16 try { 17 monkey = (Monkey) super.clone(); 18 } catch (CloneNotSupportedException e) { 19 e.printStackTrace(); 20 } 21 return monkey; 22 } 23 24 @Override 25 public String toString() { 26 return "Monkey{" + 27 "name='" + name + '\'' + 28 ", age=" + age + 29 ", friend=" + friend + 30 '}'; 31 } 32 } 33 34 class Pig { 35 String name; 36 37 public Pig(String name) { 38 this.name = name; 39 } 40 41 @Override 42 public String toString() { 43 return "Pig{" + 44 "name='" + name + '\'' + 45 '}'; 46 } 47 }
Test:

1 public static void main(String[] args) { 2 Monkey monkey = new Monkey("孙悟空", 800); 3 monkey.friend = new Pig("佩奇"); 4 5 Monkey monkey2 = (Monkey) monkey.clone(); 6 System.out.println("------------------------Monkey1------------------------"); 7 System.out.println(monkey); 8 System.out.println("monkey = " + monkey.hashCode()); 9 10 System.out.println("------------------------Monkey2------------------------"); 11 System.out.println(monkey2); 12 System.out.println("monkey2 = " + monkey2.hashCode()); 13 System.out.println("monkey == monkey2 ? " + (monkey == monkey2)); 14 System.out.println("\nmonkey.friend : " + monkey.friend.hashCode()); 15 System.out.println("monkey2.friend : " + monkey2.friend.hashCode()); 16 System.out.println("monkey.friend == monkey2.friend ? " + (monkey.friend == monkey2.friend)); 17 18 }
结果:如图,monkey2克隆了monkey,两者不是同一个实例,但是monkey引用的pig类(monkey.friend)为同一实例
Cast2:深克隆
因为Pig也要被克隆,所以也要重写Clone()方法:

1 class Pig implements Cloneable { 2 String name; 3 4 public Pig(String name) { 5 this.name = name; 6 } 7 8 @Override 9 public String toString() { 10 return "Pig{" + 11 "name='" + name + '\'' + 12 '}'; 13 } 14 15 @Override 16 public Object clone() { 17 Pig pig = null; 18 try { 19 pig = (Pig) super.clone(); 20 } catch (CloneNotSupportedException e) { 21 e.printStackTrace(); 22 } 23 return pig; 24 } 25 }
同时,Monkey类的Clone方法也进行修改(先克隆基础数据类型,在一一克隆引用的对象)
1 @Override 2 public Object clone() { 3 Monkey monkey = null; 4 try { 5 monkey = (Monkey) super.clone(); 6 } catch (CloneNotSupportedException e) { 7 e.printStackTrace(); 8 } 9 monkey.friend = (Pig) monkey.friend.clone(); 10 return monkey; 11 }
结果:
分析:这种方法确实可以实现深克隆,但假设Pig类也引用了其他对象,则需要递归克隆引用的对象,不好实现,如果引用的对象是循环链表可能会无法收敛。
2、通过序列化、反序列化实现对象的复制
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是将流转换为对象。
java.io.ObjectOutputStream:对象输出流
writeObject(Object obj) 将obj序列化,把得到的字节序列写到目标输出到流中;
java.io.ObjectInputStream:对象输入流
readObject() 读取字节序列,反序列化为新对象

1 /** 2 * @param obj 原型对象 3 * @return 克隆对象 4 */ 5 static Object SerialClone(Object obj) { 6 ByteArrayOutputStream bos = null; 7 ObjectOutputStream oos = null; 8 ByteArrayInputStream bis = null; 9 ObjectInputStream ois = null; 10 Object copyObj = null; 11 try { 12 // 序列化 13 bos = new ByteArrayOutputStream(); 14 oos = new ObjectOutputStream(bos); 15 oos.writeObject(obj); 16 // 反序列化 17 bis = new ByteArrayInputStream(bos.toByteArray()); 18 ois = new ObjectInputStream(bis); 19 copyObj = ois.readObject(); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } finally { 23 try { 24 if (ois != null) ois.close(); 25 if (bis != null) bis.close(); 26 if (oos != null) oos.close(); 27 if (bos != null) bos.close(); 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 } 32 return copyObj; 33 }
PS:序列化还有很多其他方法,不在讨论
总结:
在创建复杂对象,可以使用原型模式简化创建对象的过程,同时提高效率
克隆对象可以动态获取该对象的运行时状态
缺点:需要让每个类重写clone方法或实现Serializable接口(其实是个空接口,但是还是要implement),如果要克隆已有的类则需要修改源代码,违背OCP原则