设计模式_原型模式
作者:云都小生
概述
现实中有这么一种情况,我们需要同时用到许多个对象,这些对象都有一部分共同的特点(默认属性),但我们有时候还想要使它们有些不同(改变属性)。
例如,许多人经常跟PPT打交道,PPT中提供了多个不同的模版,当我们做PPT的时候,就可以直接使用这些模版,不用自己去制作。如果没有这些模版,我们每次都需要自己去制作,又或者是不断的copy,那该多烦。
在设计模式中,我们希望通过”模板”克隆出一个新的对象,以此节省我们许多的工作,提高工作效率,这种设计模式就是——原型模式。
简单原型模式的实现
//原型接口
interface Prototype {
void printLog();
void setName(String name);
String getName();
void setLogTxt(String Logs);
String getLogTxt();
Prototype clone();
}
//模版1
public class OneTemplate implements Prototype, Cloneable {
private String name = "默认"; //工作人员名称
private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。"; //日志内容
public void printLog()
{
System.out.println("工作人员:" + name + "\n日志内容:" + LogTxt);
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setLogTxt(String LogTxt)
{
this.LogTxt = LogTxt;
}
public String getLogTxt()
{
return LogTxt;
}
public Prototype clone()
{
Object object = null;
try
{
object = super.clone();
return (Prototype)object;
} catch (CloneNotSupportedException e)
{
e.printStackTrace();
return null;
}
}
}
//Client端
public class Client {
public static void main(String[] args) {
Prototype p1 = new OneTemplate();
Prototype p2 = p1.clone();
p2.setName("云都小生");
p2.printLog();
}
}
其实这里还有一个模板2,没有帖出来,跟模板1差不多,只不过日志内容(LogTxt)不一样。
Java有独特的对象克隆机制,需要有以下三个步骤。
1. 该”模板”类(或者克隆类)要实现Cloneable接口;
2. 该”模板”类(或者克隆类)要覆盖基类的clone()方法,并声明为public;
3. 在覆盖的clone方法中,要调用super.clone()方法;
通过这个简单的对象克隆示例,我们已经了解有对象克隆机制是多么方便。这个时候如果我们需要设计一个工作日志的程序,我们就可以定义不同的模板,需要的时候直接clone。(妈妈再也不用担心我的工作啦!)
但是,对象克隆设计还有两个比较重要的点,一个是浅克隆,另一个是深克隆。
遗难
上面我们已经设计了一个简单的日志记录程序框架,但是我们发现了一个问题,如果我们需要在日志中添加一些附件(运营文档)。我们希望这个东西也能被复制,生成一个不同的实例(附件),但是很遗憾,复制出来的附件,都指向了同一个对象。
什么意思呢?我们看一下浅克隆中的例子,你就明白了。Java中的克隆机制分为浅克隆和深克隆,浅克隆只能克隆基本数据类型的成员,而克隆引用类型成员的时候,是直接把指针到同一个引用对象。深克隆就能支持真正的克隆对象。
浅克隆
//原型接口
interface Prototype {
void printLog();
void setName(String name);
String getName();
void setLogTxt(String Logs);
String getLogTxt();
Prototype clone();
void setTxTAnnex(TxTAnnex txtAnnex);
TxTAnnex getTxTAnnex();
}
//原型1
public class OneTemplate implements Prototype, Cloneable {
private TxTAnnex txtAnnex;
private String name = "默认"; //工作人员名称
private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。"; //日志内容
public void setTxTAnnex(TxTAnnex txtAnnex)
{
this.txtAnnex = txtAnnex;
}
public TxTAnnex getTxTAnnex()
{
return txtAnnex;
}
public void printLog()
{
System.out.println(LogTxt);
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setLogTxt(String LogTxt)
{
this.LogTxt = LogTxt;
}
public String getLogTxt()
{
return LogTxt;
}
public Prototype clone()
{
Object object = null;
try
{
object = super.clone();
return (Prototype)object;
} catch (CloneNotSupportedException e)
{
e.printStackTrace();
return null;
}
}
}
//附件类
public class TxTAnnex {
String name = "运营文档"; //附件名
public void setName(String name){
this.name = name;
}
public String getName()
{
return name;
}
private void addAnnex()
{
System.out.println(name + ",文档添加成功!");
}
}
//Client端
public class Client
{
public static void main(String[] args) {
Prototype p1 = new OneTemplate();
TxTAnnex txtAnnex = new TxTAnnex();
txtAnnex.setName("公众号运营文档");
p1.setTxTAnnex(txtAnnex);
Prototype p2 = p1.clone();
p2.setName("云都小生");
System.out.println("两个日志是否相同 " + (p2 == p1));
System.out.println("两个日志中的附件是否相同 " + (p1.getTxTAnnex() == p2.getTxTAnnex()));
}
}
我们增加了一个附件类,然后在原型类中,可以添加附件,当我们克隆的附件的时候,附件都指向了同一个对象。
程序运行结果是:
两个日志是否相同 false
两个日志中的附件是否相同 true
当涉及到引用类型的克隆时,我们就需要深克隆啦。
深克隆
在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。需要注意的是能够实现序列化的对象其类必须实现 Serializable接口,否则无法实现序列化操作。
//原型接口
import java.io.IOException;
interface Prototype {
void printLog();
void setName(String name);
String getName();
void setLogTxt(String Logs);
String getLogTxt();
Prototype deepClone() throws IOException, ClassNotFoundException;
void setTxTAnnex(TxTAnnex txtAnnex);
TxTAnnex getTxTAnnex();
}
//原型1
import java.io.*;
public class OneTemplate implements Prototype, Serializable {
private TxTAnnex txtAnnex;
private String name = "默认"; //工作人员名称
private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。"; //日志内容
public void setTxTAnnex(TxTAnnex txtAnnex)
{
this.txtAnnex = txtAnnex;
}
public TxTAnnex getTxTAnnex()
{
return txtAnnex;
}
public void printLog()
{
System.out.println(LogTxt);
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setLogTxt(String LogTxt)
{
this.LogTxt = LogTxt;
}
public String getLogTxt()
{
return LogTxt;
}
public Prototype deepClone() throws IOException, ClassNotFoundException
{
//将对象写入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (Prototype)ois.readObject();
}
}
//附件原型
import java.io.Serializable;
public class TxTAnnex implements Serializable{
String name = "运营文档"; //附件名
public void setName(String name){
this.name = name;
}
public String getName()
{
return name;
}
private void addAnnex()
{
System.out.println(name + ",文档添加成功!");
}
}
//Client端
import java.io.IOException;
public class Client {
public static void main(String[] args) {
Prototype p1 = new OneTemplate();
TxTAnnex txtAnnex = new TxTAnnex();
txtAnnex.setName("公众号运营文档");
p1.setTxTAnnex(txtAnnex);
Prototype p2 = null;
try {
p2 = p1.deepClone();
} catch (Exception e)
{
System.out.println("对象克隆失败!");
System.exit(0);
}
p2.setName("云都小生");
p2.printLog();
System.out.println("两个日志是否相同" + (p2 == p1));
System.out.println("两个日志中的附件是否相同" + (p1.getTxTAnnex() == p2.getTxTAnnex()));
}
}
实现深克隆有几点需要注意的:
1. 克隆的原型类要实现Serializable类;
2. 深克隆不再像浅克隆那样要实现Cloneable类,覆盖clone方法,调用clone方法··· 而是采用写入流的方式来克隆;
3. 由于I/O操作可能会引发很多异常,必要时要抛出或者捕捉这些异常。
最主要的克隆代码在deepClone()方法里面,将自己写入流中,然后再从流取出来。这样一来,就可以实现两个附件对象的独立。
//将对象写入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (Prototype)ois.readObject();
对象克隆管理器
现在我们引入一个对象克隆管理器的概念,我们有很多不同类型的文档,分别是公众号运营文档、用户数据分析文档··· 这个时候我们需要克隆哪一个文档,只需要向对象克隆管理器传入相应的参数即可。
跟工厂三兄弟有点类似,将对象创建封装起来,在客户端,只需要调用方法即可。这里我们还需要用到单例模式,因为对象克隆管理器需要独立。
//文档接口
interface Document extends Cloneable {
void setName(String name);
String getName();
Document clone();
void printDoc();
}
//公众号运营文档
public class WecatPublicDoc implements Document
{
String name = "微信公众号运营文档";
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public Document clone()
{
Document wpd = null;
try
{
wpd = (Document)super.clone();
} catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return wpd;
}
public void printDoc()
{
System.out.println(name);
}
}
//用户数据文档
public class UserDataDoc implements Document
{
String name = "用户数据文档";
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public Document clone()
{
Document udd = null;
try
{
udd = (Document)super.clone();
} catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return udd;
}
public void printDoc()
{
System.out.println(name);
}
}
//文档克隆管理器
import java.util.Hashtable;
public class DocCloneManager {
//饿汉式单例模式
private Hashtable ht=new Hashtable();
private static DocCloneManager dcm = new DocCloneManager();
public static DocCloneManager getManager()
{
return dcm;
}
private DocCloneManager()
{
ht.put("Wecat",new WecatPublicDoc());
ht.put("User",new UserDataDoc());
}
public Document docClone(String type)
{
return ((Document)ht.get(type)).clone();
}
}
//Client端
public class Client {
public static void main(String[] args)
{
DocCloneManager dcm = DocCloneManager.getManager();
Document d1 = dcm.docClone("Wecat");
d1.printDoc();
Document d2 = dcm.docClone("Wecat");
d2.printDoc();
System.out.println(d1 == d2);
}
}
我们添加了一个单例类——对象克隆管理器,在这个管理器中我们存放了两种文档的示例。当我们需要克隆某一个文档的时候,直接传入参数即可,这样一来,客户端的代码复杂度下降了。
这个只是浅克隆的例子,当然,如果你希望这些文档里面有附件,也可以改成深克隆。
优缺点
优点:通过原型模式,我们能高效的克隆某些对象,简化对象创建的过程,可扩展性也很强。
缺点:如果我们需要给克隆类的克隆方法进行修改,就必须在内部修改,因为克隆方法在该克隆类内部。在实现深克隆的时候,需要用到I/O的操作,会比较复杂。
2017/10/9 11:53:02 @Author:云都小生(Cloudking)
参考文章:史上最全设计模式导学目录(完整版)