[TOC]
欢迎关注千羽的公众号Gitee:gitee.com/nateshao/de…
Github:github.com/nateshao/de…
1. 原型模式概述
通过复制一个原型对象得到多个与原型对象一模一样的新对象
定义:
原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
- 工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
- 创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
- 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
- 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象
2. 原型模式的结构与实现
原型模式的结构
原型模式包含以下3个角色:
- Prototype(抽象原型类)
- ConcretePrototype(具体原型类)
- Client(客户类)
浅克隆与深克隆
浅克隆(Shallow Clone): 当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。
**深克隆(Deep Clone):**除了对象本身被复制外,对象所包含的所有成员变量也将被复制。
通用的克隆实现方法
Java语言中的clone()方法和Cloneable接口
在Java语言中,提供了一个clone()方法用于实现浅克隆,该方法使用起来很方便,直接调用super.clone()方法即可实现克隆
浅克隆
WeeklyLog.java
package com.nateshao.prototype.shallowclone;
public class WeeklyLog implements Cloneable {
// 为了简化设计和实现,假设一份工作周报中只有一个附件对象,
// 实际情况中可以包含多个附件,可以通过List等集合对象来实现
private Attachment attachment;
private String name;
private String date;
private String content;
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public void setName(String name) {
this.name = name;
}
public void setDate(String date) {
this.date = date;
}
public void setContent(String content) {
this.content = content;
}
public Attachment getAttachment() {
return (this.attachment);
}
public String getName() {
return (this.name);
}
public String getDate() {
return (this.date);
}
public String getContent() {
return (this.content);
}
//使用clone()方法实现浅克隆
public WeeklyLog clone() {
Object obj = null;
try {
obj = super.clone();
return (WeeklyLog)obj;
}
catch(CloneNotSupportedException e) {
System.out.println("不支持复制!");
return null;
}
}
}
Attachment.java
package com.nateshao.prototype.shallowclone;
public class Attachment {
private String name; //附件名
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void download() {
System.out.println("下载附件,文件名为" + name);
}
}
Client.java
package com.nateshao.prototype.shallowclone;
public class Client {
public static void main(String args[]) {
WeeklyLog log_previous, log_new;
log_previous = new WeeklyLog(); //创建原型对象
Attachment attachment = new Attachment(); //创建附件对象
log_previous.setAttachment(attachment); //将附件添加到周报中
log_new = log_previous.clone(); //调用克隆方法创建克隆对象
//比较周报
System.out.println("周报是否相同? " + (log_previous == log_new));
//比较附件
System.out.println("附件是否相同? " + (log_previous.getAttachment() == log_new.getAttachment()));
}
}
深克隆
WeeklyLog.java
package com.nateshao.prototype.deepclone;
import java.io.*;
/**
* @date Created by 邵桐杰 on 2021/10/14 21:59
* @微信公众号 程序员千羽
* @个人网站 www.nateshao.cn
* @博客 https://nateshao.gitee.io
* @GitHub https://github.com/nateshao
* @Gitee https://gitee.com/nateshao
* Description:
*/
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 void setName(String name) {
this.name = name;
}
public void setDate(String date) {
this.date = date;
}
public void setContent(String content) {
this.content = content;
}
public Attachment getAttachment() {
return (this.attachment);
}
public String getName() {
return (this.name);
}
public String getDate() {
return (this.date);
}
public String getContent() {
return (this.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();
}
}
Attachment.java
package com.nateshao.prototype.deepclone;
import java.io.Serializable;
/**
* @date Created by 邵桐杰 on 2021/10/14 22:00
* @微信公众号 程序员千羽
* @个人网站 www.nateshao.cn
* @博客 https://nateshao.gitee.io
* @GitHub https://github.com/nateshao
* @Gitee https://gitee.com/nateshao
* Description:
*/
public class Attachment implements Serializable {
private String name; //附件名
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void download() {
System.out.println("下载附件,文件名为" + name);
}
}
Client.java
package com.nateshao.prototype.deepclone;
/**
* @date Created by 邵桐杰 on 2021/10/14 22:00
* @微信公众号 程序员千羽
* @个人网站 www.nateshao.cn
* @博客 https://nateshao.gitee.io
* @GitHub https://github.com/nateshao
* @Gitee https://gitee.com/nateshao
* Description:
*/
public class Client {
public static void main(String args[]) {
WeeklyLog log_previous, log_new = null;
log_previous = new WeeklyLog(); //创建原型对象
Attachment attachment = new Attachment(); //创建附件对象
log_previous.setAttachment(attachment); //将附件添加到周报中
try {
log_new = log_previous.deepClone(); //调用深克隆方法创建克隆对象
}
catch(Exception e) {
System.err.println("克隆失败!");
}
//比较周报
System.out.println("周报是否相同? " + (log_previous == log_new));
//比较附件
System.out.println("附件是否相同? " + (log_previous.getAttachment() == log_new.getAttachment()));
}
}
3. 原型模式的应用实例
实例说明
在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。
试使用原型模式对该OA系统中的工作周报创建模块进行改进。
实例类图
工作周报对象被成功复制,但是附件对象并没有复制,实现了浅克隆
深克隆解决方案
工作周报类WeeklyLog和附件类Attachment实现Serializable接口
修改工作周报类WeeklyLog的clone()方法
- WeeklyLog: 具体原型类
- Attachment: 具体原型类
- Client
周报是否相同? false
附件是否相同? false
工作周报对象和附件对象都成功复制,实现了深克隆
4. 原型管理器
定义:原型管理器(Prototype Manager)将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得
结构:
实现
import java.util.*;
public class PrototypeManager {
private Hashtable prototypeTable=new Hashtable(); //使用Hashtable存储原型对象
public PrototypeManager() {
prototypeTable.put("A", new ConcretePrototypeA());
prototypeTable.put("B", new ConcretePrototypeB());
}
public void add(String key, Prototype prototype) {
prototypeTable.put(key,prototype);
}
public Prototype get(String key) {
Prototype clone = null;
clone = ((Prototype) prototypeTable.get(key)).clone(); //通过克隆方法创建新对象
return clone;
}
}
5. 原型模式的优缺点与适用环境
模式优点
- 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
- 扩展性较好
- 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
- 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作
模式缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
模式适用环境
- 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象
- Ctrl + C -> Ctrl + V