原型模式
1. 原型模式概述
原型模式指:通过一个原型对象克隆出多个一模一样的对象。
在使用原型模式时,首先需要创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。
使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式时一种对象创建型模式。
1.1 工作原理
将一个原型对象传给要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。
通过克隆方法所创建的对象是全新的,在内存中有新的地址,对克隆对象所产生的对象进行修改不会对原型对象产生任何影响。
1.2 包含角色
- Prototype (抽象原型类)
是声明克隆方法的接口,是所有具体类型类的公共父类。抽象类/ 接口/ 具体实现类
- ConcretePrototype (具体原型类)
实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象
- Client
让一个原型对象克隆自身,从而创建一个新的对象。客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,扩展性加强了。
2. 原型模式的实现
2.1 通用实现方法
class ConcretePrototype implements Prototype {
private String attr; //成员属性
public void setAttr(String attr) {
this.attr = attr;
}
public String getAttr() {
return this.attr;
}
public Prototype clone() //克隆方法
{
Prototype prototype = new ConcretePrototype(); //创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}
2.2 Java提供的clone()方法
所有的Java类都继承自java.lang.Object。事实上,Object提供了clone()方法,可以将一个Java对象复制一份。可以直接使用这个方法来实现克隆的。
class ConcretePrototype implements Cloneable {
public Prototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return (Prototype )object;
}
}
Java中的clone()满足:
- 对于任何对象,都有
x.clone() != x
,即克隆方法创建了新的对象 - 对于任何对象,都有
x.clone().getClass() == x.getClass()
,即克隆对象与原型对象的类型一样 - 派生类需要实现Cloneable接口
3. 原型模式实现工作周报快速创建
快速创建周报结构图
//工作周报WeeklyLog:具体原型类,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码
class WeeklyLog implements Cloneable
{
private String name;
private String date;
private String content;
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 String getName() {
return (this.name);
}
public String getDate() {
return (this.date);
}
public String getContent() {
return (this.content);
}
//克隆方法clone(),此处使用Java语言提供的克隆机制
public WeeklyLog clone()
{
Object obj = null;
try {
obj = super.clone();
return (WeeklyLog)obj;
} catch(CloneNotSupportedException e) {
System.out.println("不支持复制!");
return null;
}
}
}
测试代码:
class Client
{
public static void main(String args[])
{
WeeklyLog log_previous = new WeeklyLog(); //创建原型对象
log_previous.setName("张无忌");
log_previous.setDate("第12周");
log_previous.setContent("这周工作很忙,每天加班!");
System.out.println("****周报****");
System.out.println("周次:" + log_previous.getDate());
System.out.println("姓名:" + log_previous.getName());
System.out.println("内容:" + log_previous.getContent());
System.out.println("--------------------------------");
WeeklyLog log_new;
log_new = log_previous.clone(); //调用克隆方法创建克隆对象
log_new.setDate("第13周");
System.out.println("****周报****");
System.out.println("周次:" + log_new.getDate());
System.out.println("姓名:" + log_new.getName());
System.out.println("内容:" + log_new.getContent());
}
}
4. 浅克隆与深克隆
在java中,数据类型分为值类型和引用类型,值类型包括int,double, byte, boolean, char等,引用类型包括类,接口,数组等。浅克隆与深克隆的区别在于是否支持引用类型的成员变量的复制。
4.1 浅克隆
浅克隆示意图
如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说二者的成员变量指向了相同的内存地址。在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
带附件的周报结构图
附件类代码:
//附件类
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);
}
}
工作周报类代码
//工作周报WeeklyLog
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;
}
}
}
客户端代码:
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()));
}
}
4.2 深克隆
无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象。即深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
深克隆示意图
如果要实现深克隆,在Java中可以通过序列化Serialization等方式来实现。
序列化,将对象写到流里,通过序列化的拷贝不仅可以复制对象本身,也可以复制其引用的成员对象。因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
带附件的周报结构图
附件类:
import java.io.*;
//附件类
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);
}
}
工作周报类代码实现:
import java.io.*;
//工作周报类
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();
}
}
客户端代码:
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()));
}
}
Tips: java中的Cloneable和Serializable接口的代码很简单,都是空接口,也成为标识接口,其中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能。
4.3 Java中实现深克隆的方法浅析
4.3.1 Apache Commons Lang
SerializationUtils#clone, will perform a deep copy when all classes in the object graph implement the serializable interface.
4.3.2 Json Serialization with Gson
No need for Serializable interface
gson.fromJson(gson.toJson(userA), User.class);
4.3.3 constructor
直接handmade, 用constructor生成一个新的想要的对象。
5. 原型管理器
原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。
带原型管理器的原型模式图
5.1 实例-公文管理器
公文管理器结构图
代码实现:
import java.util.*;
//抽象公文接口,也可定义为抽象类,提供clone()方法的实现,将业务方法声明为抽象方法
interface OfficialDocument extends Cloneable
{
public OfficialDocument clone();
public void display();
}
//可行性分析报告(Feasibility Analysis Report)类
class FAR implements OfficialDocument
{
public OfficialDocument clone()
{
OfficialDocument far = null;
try
{
far = (OfficialDocument)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("不支持复制!");
}
return far;
}
public void display()
{
System.out.println("《可行性分析报告》");
}
}
//软件需求规格说明书(Software Requirements Specification)类
class SRS implements OfficialDocument
{
public OfficialDocument clone()
{
OfficialDocument srs = null;
try
{
srs = (OfficialDocument)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("不支持复制!");
}
return srs;
}
public void display()
{
System.out.println("《软件需求规格说明书》");
}
}
//原型管理器(使用饿汉式单例实现)
class PrototypeManager
{
//定义一个Hashtable,用于存储原型对象
private Hashtable ht=new Hashtable();
private static PrototypeManager pm = new PrototypeManager();
//为Hashtable增加公文对象
private PrototypeManager()
{
ht.put("far",new FAR());
ht.put("srs",new SRS());
}
//增加新的公文对象
public void addOfficialDocument(String key,OfficialDocument doc)
{
ht.put(key,doc);
}
//通过浅克隆获取新的公文对象
public OfficialDocument getOfficialDocument(String key)
{
return ((OfficialDocument)ht.get(key)).clone();
}
public static PrototypeManager getPrototypeManager()
{
return pm;
}
}
客户端代码如下:
class Client
{
public static void main(String args[])
{
//获取原型管理器对象
PrototypeManager pm = PrototypeManager.getPrototypeManager();
OfficialDocument doc1,doc2,doc3,doc4;
doc1 = pm.getOfficialDocument("far");
doc1.display();
doc2 = pm.getOfficialDocument("far");
doc2.display();
System.out.println(doc1 == doc2);
doc3 = pm.getOfficialDocument("srs");
doc3.display();
doc4 = pm.getOfficialDocument("srs");
doc4.display();
System.out.println(doc3 == doc4);
}
}
在PrototypeManager中定义了一个Hashtable类型的集合对象,使用“键值对”来存储原型对象,客户端可以通过Key(如“far”或“srs”)来获取对应原型对象的克隆对象。PrototypeManager类提供了类似工厂方法的getOfficialDocument()方法用于返回一个克隆对象。
6. 总结
6.1 优点
- 简化对象的创建过程
- 客户端可以针对抽象原型类进行编程,具体原型类写在配置文件中
- 技工简化的创建结构
6.2 缺点
- 每一个类有自己的克隆方法,且位于一个类的内部,对已有类进行改造的时候,需要修改源代码
- 深克隆复杂
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com
文章标题:原型模式
文章字数:2.8k
本文作者:Leilei Chen
发布时间:2020-02-03, 07:02:53
最后更新:2020-02-04, 11:22:54
原始链接:https://www.llchen60.com/%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。