原型模式
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象。
原型模式的核心在于如何实现克隆方法,下面将介绍两种在Java语言中常用的克隆实现方法:
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; } }
思考
能否将上述代码中的clone()方法写成:public Prototype clone() { return this; }?给出你的理由。
个人答:不能,因为这个返回的是对象的引用不是对象本身。
在客户类中我们只需要创建一个ConcretePrototype对象作为原型对象,然后调用其clone()方法即可得到对应的克隆对象,如下代码所示:
//原型管理器(使用饿汉式单例实现) 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); } }
编译并运行程序,输出结果如下:
《可行性分析报告》
《可行性分析报告》
false
《软件需求规格说明书》
《软件需求规格说明书》
false
Java语言提供的clone()方法:
需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。如下代码所示:
在客户端创建原型对象和克隆对象也很简单,如下代码所示:
7.3 完整解决方案
开发人员决定使用原型模式来实现工作周报的快速创建,快速创建工作周报结构图如图7-3所示:
WeeklyLog充当具体原型类,Object类充当抽象原型类,clone()方法为原型方法。WeeklyLog类的核心代码:
note:在实现cloneable接口的类中,实现clone方法时,切记不要调用this.clone,会发生死循环。
编写如下客户端测试代码:
7.4 带附件的周报
如果使用上述原型模式来复制周报,周报虽然可以复制,但是周报的附件并不能复制
先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
1.浅克隆
在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制,原型对象和克隆对象的成员变量指向相同的内存地址。
图7-4 浅克隆示意图
note:
引用类型包括类、接口、数组等复杂类型
我们首先使用浅克隆来实现工作周报和附件类的复制,其结构如图7-5所示:
图7-5 带附件的周报结构图(浅克隆)
客户端代码如下所示:
由于使用的是浅克隆技术,因此工作周报对象复制成功,通过“==”比较原型对象和克隆对象的内存地址时输出false;但是比较附件对象的内存地址时输出true,说明它们在内存中是同一个对象。
2.深克隆
在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制
通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
工作周报类WeeklyLog不再使用Java自带的克隆机制,而是通过序列化来从头实现对象的深克隆,我们需要重新编写clone()方法,修改后的代码如下:
客户端代码如下所示:
编译并运行程序,输出结果如下:
7.5 原型管理器的引入和实现
原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。
我们使用带原型管理器的原型模式实现公文管理器的设计,其结构如图7-9所示: