一、概述
《西游记》中,孙悟空拔毛变小猴的故事人人皆知。在面向对象软件设计领域,根据自己的形状复制(克隆)出与自己一摸一样的技巧叫做原型模式。
原型模式的定义如下:
原型模式(Prototype Pattern):使用原型实例指定待创建对象的类型,并通过复制这个原型来创建新的对象。
二、结构与实现
Prototype: 抽象原形类
ConcretePrototype: 具体原形类
Client: 客户类
三、浅克隆
根据原形对象的引用类型的成员变量是否被复制,原形模式的两种克隆机制分为浅克隆和深克隆。
在浅克隆中,如果原型对象的成员变量是值类型(如int,double,byte,boolean,char等基本数据类型),则复制一份,如果是引用类型(如类、接口、数组等复杂数据类型),则将引用对象的地址复制一份给克隆对象。
所有的java类均继承java.lang.Object类,Object类提供了一个clone()方法,可以将一个Java对象复制一份。因此可以直接使用Object的clone()方法实现原型对象的浅克隆。但是能实现克隆的原型对象类必须实现Cloneable接口,标识这个类支持复制。如果一个类没实现Cloneable接口,但调用了clone()方法,java编译器会抛出CloneNotSupportedException异常。
四、深克隆
在深克隆中,原型对象的成员对象无论是值类型还是引用类型都将复制一份。
在java中,可以使用序列化实现深克隆。把原型对象写入流中,然后把该流写出到克隆对象,这个过程不但可以复制原型对象的值类型成员变量,还可以复制原型对象的引用类型成员变量,从而实现深克隆。但实现序列化的类必须实现Serializable接口。
五、使用案例
案例:某公司员工每周都要用系统提交周报,但大家发现每周的周报都大同小异,内容很相似,所以急需快速创建相同或相似周报的功能,包括周报的附件。
请用原型模式改进现有系统,增加复制周报功能。
浅克隆解决方案:
package com.mzy.shejimoshi.PrototypePatternOfShallow;
public class Attachment {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void download() {
System.out.println("下载附件,文件名为"+name);
}
}
package com.mzy.shejimoshi.PrototypePatternOfShallow;
public class WeeklyLog implements Cloneable {
private Attachment attachment;
private String name;
private String date;
private String content;
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public Attachment getAttachment() {
return attachment;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
@Override
public WeeklyLog clone() {
Object obj = null;
try {
obj = super.clone();
return (WeeklyLog)obj;
} catch (CloneNotSupportedException e) {
System.out.println("不支持复制!");
return null;
}
}
}
package com.mzy.shejimoshi.PrototypePatternOfShallow;
import java.nio.file.WatchEvent;
public class Client {
public static void main(String [] args) {
WeeklyLog log_previous = new WeeklyLog();
Attachment attachment = new Attachment();
log_previous.setAttachment(attachment);
WeeklyLog log_new = log_previous.clone();
System.out.println("周报是否相同:"+(log_previous == log_new));
System.out.println("附件是否相同:"+(log_previous.getAttachment() == log_new.getAttachment()));
}
}
执行结果如下:
周报是否相同:false
附件是否相同:true
深克隆解决方案如下:
package com.mzy.shejimoshi.PrototypePatternOfDeep;
import java.io.Serializable;
public class Attachment implements Serializable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void download() {
System.out.println("下载附件,文件名为"+name);
}
}
package com.mzy.shejimoshi.PrototypePatternOfDeep;
import java.io.*;
public class WeeklyLog implements Serializable {
private Attachment attachment;
private String name;
private String date;
private String content;
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public Attachment getAttachment() {
return attachment;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public WeeklyLog deepClone() throws IOException, ClassNotFoundException, OptionalDataException{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (WeeklyLog)ois.readObject();
}
}
package com.mzy.shejimoshi.PrototypePatternOfDeep;
public class Client {
public static void main(String [] args) {
WeeklyLog log_previous = new WeeklyLog();
WeeklyLog log_new = null;
Attachment attachment = new Attachment();
log_previous.setAttachment(attachment);
try {
log_new = log_previous.deepClone();
} catch (Exception e) {
System.out.println("克隆失败");
}
System.out.println("周报是否相同:"+(log_previous == log_new));
System.out.println("附件是否相同:"+(log_previous.getAttachment() == log_new.getAttachment()));
}
}
执行结果如下:
周报是否相同:false
附件是否相同:false