可以通过一个原型对象,克隆出多个一模一样的对象
1. 引出问题
图中有两份工作周报,两份周报的大部分内容完全一致,只有少部分不同
原有系统在创建新的周报时,只能生成一份空白周报,需要从零开始填写,即使只是需要复制粘贴,也同样需要付出额外的工作成本
改进思路:
- 可以将已有的周报保存成周报模板
- 在创建新周报时,可以自由选择运用周报模板或者创建空周报
2. 原型模式介绍
定义:使用原型实例指定创建对象的种类,并且通过克隆这些原型来创建新的对象
工作原理:将需要克隆的原型对象传递给负责创建新对象的对象,这个对象再通过请求原型对象克隆原型对象自身,从而实现新对象的创建过程
克隆出的对象与原型对象之间的关系是完全独立的,对新对象进行修改不会影响原型对象
原型模式包含三种角色:
- Prototype(抽象原型类)
- ConcretePrototype(具体原型类)
- Client(客户类)
抽象原型类中定义了clone方法,具体原型类作为抽象原型类的子类,对clone方法进行实现,Client类中有一个Prototype类的prototype属性,可以通过多态,接收具体原型类的实例,通过operation方法调用clone方法,从而完成原型对象的克隆
原型模式的核心是如何对clone方法进行实现,以下给出两种经典实现方法:
2.1. 通用实现方法
public class ConcretePrototype implements Prototype{
private String attr; // 成员变量
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
@Override
public Prototype clone() {
Prototype prototype = new ConcretePrototype();
prototype.setAttr(this.attr);
return prototype;
}
}
2.2. Java语言自带的clone方法
在Object类中已经写好了clone方法
public class ConcretePrototype implements Cloneable{
@Override
public Prototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("未实现Cloneable接口");
}
return (Prototype) object;
}
}
使用自带的clone方式需要实现Cloneable接口
3. 使用原型模式重构周报案例
WeeklyLog类代码如下
public class WeeklyLog implements Cloneable{
private String name;
private String date;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
protected WeeklyLog clone() {
Object obj = null;
try {
obj = super.clone();
return (WeeklyLog) obj;
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
return null;
}
}
}
改造之后,可以根据已有的周报作为模板生成新周报,而无需每次都从头开始
4. 带有附件的周报
实际应用中,若周报带有附件,那么该如何运用模板模式进行克隆呢?
4.1. 浅克隆
浅克隆中,如果原型对象中的成员变量是值类型,则复制一份到新对象中;若成员变量是引用类型,则只是简单地将引用对象的地址复制一份给新对象,即原型对象的成员变量和新对象中的成员变量会指向同一个地址
Java中Object类的clone方式实际上就是一种浅克隆
作如下验证:
原型对象中添加一个引用型的成员变量attachment表示附件
public class Attachment {
private String name; //附件名
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download(){
System.out.println("下载附件,文件名: " + name);
}
}
修改原型类,原型类中添加成员变量attachment,并实现getter和setter方法
编写测试类:
public class WeeklyLogTest {
public static void main(String[] args) {
WeeklyLog preLog, newLog;
preLog = new WeeklyLog();
Attachment attachment = new Attachment();
preLog.setAttachment(attachment);
newLog = preLog.clone();
System.out.println("周报是否相同?: " + (newLog == preLog));
System.out.println("附件是否相同?: " + (newLog.getAttachment() == preLog.getAttachment()));
}
}
测试结果如下:
4.2. 深克隆
深克隆中,无论原型对象的成员变量是值类型还是引用类型,都会复制一份新的给新对象
Java中的深克隆一般通过序列化(Serialization)的方式实现,序列化实际上就是将对象写到流中,写到流中的对象是原有对象的一个复制体,而原有对象不变。通过序列化实现的克隆,不仅可以克隆对象本身,还可以克隆对象的引用对象。
修改附件类,实现Serializable接口
public class Attachment implements Serializable {
private String name; //附件名
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download(){
System.out.println("下载附件,文件名: " + name);
}
}
周报类,实现Serializable接口,将克隆方法改为deepClone:
public class WeeklyLog implements Serializable {
private String name;
private String date;
private String content;
private Attachment attachment;
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
protected WeeklyLog deepClone() throws Exception{
//将对象写到流中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (WeeklyLog) ois.readObject();
}
}
运行测试类,结果如下:
5. 原型管理器的引入和实现
原型管理器将多个原型对象存储在集合中供客户端使用,结构如下所示
5.1. 实例 —— 公文管理器
A公司在日常办公中有许多种类型的公文需要处理, 如:《可行性分析报告》、《立项建议书》、《软件需求规格说明书》、《项目进展报告》等。可以设计一个公文管理器,在需要的时候按照不同的模板快速创建新的公文
抽象公文接口:OfficialDocument
public interface OfficialDocument extends Cloneable{
OfficialDocument clone();
void display();
}
《可行性分析报告》类:FAR
public class FAR implements OfficialDocument{
@Override
public OfficialDocument clone() {
OfficialDocument far = null;
try {
far = (OfficialDocument) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return far;
}
@Override
public void display() {
System.out.println("可行性分析报告...");
}
}
《软件需求规格说明书》类:SRS
public class SRS implements OfficialDocument{
@Override
public OfficialDocument clone() {
OfficialDocument srs = null;
try {
srs = (OfficialDocument) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return srs;
}
@Override
public void display() {
System.out.println("软件需求规格说明书...");
}
}
原型管理类:PrototypeManager(采用饿汉式单例实现)
public class PrototypeManager {
//创建HashTable存储原型对象
private Hashtable<String, OfficialDocument> ht = new Hashtable<>();
private static final PrototypeManager pm = new PrototypeManager();
//初始化HashTable
private PrototypeManager(){
ht.put("far", new FAR());
ht.put("srs", new SRS());
}
//为HashTable增加新的原型对象
public void addOfficialDocument(String key, OfficialDocument doc){
ht.put(key, doc);
}
//通过浅克隆获取新的原型对象
public OfficialDocument getOfficialDocument(String key){
OfficialDocument officialDocument = ht.get(key);
return officialDocument.clone();
}
public static PrototypeManager getPrototypeManager(){
return pm;
}
}
客户端:Client
public class Client {
public static void main(String[] args) {
PrototypeManager pm = PrototypeManager.getPrototypeManager();
OfficialDocument doc1 = pm.getOfficialDocument("far");
OfficialDocument doc2 = pm.getOfficialDocument("far");
doc1.display();
doc2.display();
System.out.println(doc1 == doc2);
OfficialDocument doc3 = pm.getOfficialDocument("srs");
OfficialDocument doc4 = pm.getOfficialDocument("srs");
doc3.display();
doc4.display();
System.out.println(doc3 == doc4);
}
}
运行结果如下: