第十二章 原始模型模式
原始模型模式属于对象的创建模式。通过给出一个原型对象来指明所要创建的对象模型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是原始模型模式的用意。
java对象的复制
java.lang.Object.clone()方法
java的所有类都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制:
protected Object clone()
子类当然可以把这个方法置换掉,提供满足自己的复制方法。对象的复制有一个基本问题,就是对象通常都有对其他的对象的引用。当使用Object类的clone()方法来复制一个对象时,此对象对其他对象的引用也同时会被复制一份。
java语言提供的Cloneable接口只起一个作用,就是在运行时期通知java虚拟机可以安全地在这个类上使用clone方法。通过调用这个clone方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone方法会抛出CloneNotSupportException异常。
代码如下:
package com.javapatterns.prototype.panda; public class PandaToClone implements Cloneable { private int height, weight, age; public PandaToClone (int height, int weight) { this.age = 0; this.weight = weight; this.height = height; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public int getHeight() { return height; } public int getWeight() { return weight; } public Object clone() { PandaToClone temp = new PandaToClone(height, weight); temp.setAge(age); return (Object) temp; } }
客户端调用代码:
package com.javapatterns.prototype.panda; public class Client { private static PandaToClone thisPanda , thatPanda; public static void main(String[] args) { thisPanda = new PandaToClone(15, 25); thisPanda.setAge(3); // Create the second object by cloning the first thatPanda = (PandaToClone) thisPanda.clone(); // Now describe these objects on the system console : System.out.println(" Age of this panda : " + thisPanda.getAge()); System.out.println(" height : " + thisPanda.getHeight()); System.out.println(" weight : " + thisPanda.getWeight()); System.out.println(" Age of that panda : " + thatPanda.getAge()); System.out.println(" height : " + thatPanda.getHeight()); System.out.println(" weight : " + thatPanda.getWeight()); } }
在运行时,客户端首先创建了一个PandaToClone的实例,并且给各个性质赋值。然后将此对象复制一份。
克隆满足的条件:
clone()方法将对象复制了一份并返还给调用者,所谓“复制”的含义与clone()方法是怎么实现的有关。一般而言,clone()方法满足以下的描述:
1.对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
2.对任何的对象x,都有x.clone().getClass == x.getClass(),换言之,克隆对象与原对象的类型一样。
3.如果对象x的equals()方法定义恰当的话,那么x.clone().equals(x)应当是成立的。
在java语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。java言语的设计师在设计自己的clone()方法时,也应当遵守这三个条件。
一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。本书希望遵守所有的三条。
equals()方法的讨论
下面给出java.lang.Object的源代码:
public boolean equals(Object obj) { return (this == obj); }
也就是说,当两个变量指向同一个对象时,equals()方法才会返还true。很显然,这并不适合与所有需要被克隆的对象。
假设被克隆的对象按照它们的内部状态是否可变,划分成可变对象和不变对象的话,那么可变对象和不变对象所提供的equals()方法的工作方式应当是不同的。
可变对象只有当它们是同一个对象时,equals()才会返还true,所以这样的类型可以直接从java.lang.Object继承这个方法。不变对象必须含有相同的状态才可能满足这个条件,因此不变类型必须自行实现这个equals()方法。
一个典型的例子就是String对象。java 的String对象都是不变对象,而不变对象具有不会改变的内部状态;具有相同内部状态的String对象对于客户端就没有区别。这样一来,两个String对象的比较就是应当是他们的内部状态值的比较。
原始模型模式的结构
原始模型模式有两种表现形式:第一种是简单形式,第二种是登记形式。
简单形式的原始模型模式:
第一种形式的原始模型模式的类图如下:
实现代码如下:
客户端:
package com.javapatterns.prototype; public class Client { public void operation(Prototype example) { Prototype p = (Prototype) example.clone(); } private Prototype prototype; }
抽象原型角色:
package com.javapatterns.prototype; public interface Prototype extends Cloneable { Object clone(); }
具体原型角色:
package com.javapatterns.prototype; public class ConcretePrototype implements Prototype { public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { //write your code here return null; } } }
在熊猫克隆的例子里,原型角色由接口Cloneable扮演,具体原型角色由PandaToClone类扮演。这意味着,通常在java语言中实现Clone方法的办法就是这种形式的原始模型模式的实现。
登记形式的原始模型模式
类图如下:
示意性代码如下:
抽象原型角色Prototype源代码:
package com.javapatterns.prototype.manager; public interface Prototype extends Cloneable { public Object clone(); }
具体原型角色:
package com.javapatterns.prototype.manager; public class ConcretePrototype implements Prototype { public synchronized Object clone() { Prototype temp = null; try { temp = (Prototype) super.clone(); return temp; } catch(CloneNotSupportedException e) { System.out.println("Clone failed."); } finally { return temp; } } }
原型管理器角色保持一个聚类,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。原型管理器类的示意性源代码如下:
package com.javapatterns.prototype.manager; import java.util.Vector; public class PrototypeManager { public void add(Prototype object) { objects.add(object); } public Prototype get(int i) { return (Prototype) objects.get(i); } public int getSize() { return objects.size(); } private Vector objects = new Vector(); }
客户端Client类源代码如下:
public class Clinet { private PrototypeManager mgr; private Prototype prototype; public void registerPrototype() { prototype = new ConcretePrototype(); Prototype copytype = (Prototype)prototype.clone(); mrg.add(copytype); } }
两种形式的比较
如果需要创建的原型对象数目比较少而且比较固定的话,可以采取第一种形式,也即简单形式的原始模型模式。在这种情况下,原型对象的引用可以由客户端自己保存。
如果要创建的原型对象数目不固定的话,可以采用第二种形式。在这种情况下,客户端并不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个原型对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象引用;如果没有,客户端就需要自行复制此原型对象。
模式的实现:深复制和浅复制
正如前面所说的,复制或克隆有两种方式。这两种方式分别叫做浅复制和深复制。
浅复制:
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制:
被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
利用串行化来做深复制
把对象写到流里的过程是串行化过程,但是在java 程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜”过程;而把对象从流中读出来的并行化过程则叫做“解冻”或者“回鲜”过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,java腌菜还可以回鲜。
在java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象写到一个流里,再从流里读回来,便可以重建对象,代码如下:
public Object deepClone() { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this); ByteArrayInputStream bi = ByteArrayInputStream(); ObjectInputStream oi = new ObjectInputStream(bi); return (io.readObject()); }
下面给出最终的例子:
齐天大圣持有一个猢狲(Monkey)的实例,Monkey有一个棒子的实例:
下面是浅复制代码:
package com.javapatterns.prototype.monkeyking; import java.util.Date; public class TheGreatestSage { private Monkey monkey = new Monkey(); public void change() { Monkey copyMonkey; for (int i = 0 ; i < 2000; i++){} copyMonkey = (Monkey) monkey.clone(); System.out.println("Monkey King's birth date=" + monkey.getBirthDate() ); System.out.println("Copy monkey's birth date=" + copyMonkey.getBirthDate() ); System.out.println("Monkey King == Copy Monkey? " + (monkey == copyMonkey)); System.out.println("Monkey King's Staff == Copy Monkey's Staff? " + (monkey.getStaff() == copyMonkey.getStaff())); } public static void main(String[] args) { TheGreatestSage sage = new TheGreatestSage(); sage.change(); } }
Monkey类:
package com.javapatterns.prototype.monkeyking; import java.util.Date; public class Monkey implements Cloneable { private int height; private int weight; private GoldRingedStaff staff; private Date birthDate; public Monkey() { this.birthDate = new Date(); this.staff = new GoldRingedStaff(); } public Object clone() { Monkey temp = null; try { temp = (Monkey) super.clone(); } catch(CloneNotSupportedException e) { System.out.println("Clone failed"); } finally { return temp; } } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public GoldRingedStaff getStaff() { return staff; } }
金箍棒类:
package com.javapatterns.prototype.monkeyking; import java.util.Date; public class GoldRingedStaff implements Cloneable { private float height = 100.0F; private float diameter = 10.0F; public GoldRingedStaff() { //write your code here } public void grow() { this.diameter *= 2.0; this.height *= 2; } public void shrink() { this.diameter /= 2; this.height /= 2; } public void move() { //write your code for moving the staff } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } public float getDiameter() { return diameter; } public void setDiameter(float diameter) { this.diameter = diameter; } }
打印出来的结果,首先,复制的大圣本尊具有和原始的大圣本尊对象一样的birthDay,而本尊对象并不相等,这表明他们二者是克隆的关系;其次,复制的大圣本尊多持有的金箍棒和原始的大圣本尊所持有的金箍棒是一样的,这表明他们的金箍棒根本就是一根。
浅复制,齐天大圣的所有化身所持有的金箍棒的引用都指向一个对象。
下面给出深复制的例子:
齐天大圣类:
package com.javapatterns.prototype.monkeyking2; import java.util.Date; import java.io.IOException; import java.lang.ClassNotFoundException; public class TheGreatestSage { private Monkey monkey = new Monkey(); public void change() throws IOException, ClassNotFoundException { Monkey copyMonkey; for (int i = 0 ; i < 2000; i++){} copyMonkey = (Monkey) monkey.deepClone(); System.out.println("Monkey King's birth date=" + monkey.getBirthDate() ); System.out.println("Copy monkey's birth date=" + copyMonkey.getBirthDate() ); System.out.println("Monkey King == Copy Monkey? " + (monkey == copyMonkey)); System.out.println("Monkey King's Staff == Copy Monkey's Staff? " + (monkey.getStaff() == copyMonkey.getStaff())); } public static void main(String[] args) throws IOException, ClassNotFoundException { TheGreatestSage sage = new TheGreatestSage(); sage.change(); } }
Monkey类: package com.javapatterns.prototype.monkeyking2; import java.util.Date; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.IOException; import java.io.OptionalDataException; import java.io.Serializable; public class Monkey implements Cloneable, Serializable { private int height; private int weight; private GoldRingedStaff staff; private Date birthDate; public Monkey() { this.birthDate = new Date(); this.staff = new GoldRingedStaff(); } public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException { //write to stream ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this); //read from stream ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return (oi.readObject()); } public Object clone() { Monkey temp = null; try { temp = (Monkey) super.clone(); } catch(CloneNotSupportedException e) { System.out.println("Clone failed"); } finally { return temp; } } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public GoldRingedStaff getStaff() { return staff; } }
金箍棒类:
package com.javapatterns.prototype.monkeyking2; import java.util.Date; import java.io.Serializable; public class GoldRingedStaff implements Cloneable, Serializable { private float height = 100.0F; private float diameter = 10.0F; public GoldRingedStaff() { //write your code here } public void grow() { this.diameter *= 2.0; this.height *= 2; } public void shrink() { this.diameter /= 2; this.height /= 2; } public void move() { //write your code for moving the staff } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } public float getDiameter() { return diameter; } public void setDiameter(float diameter) { this.diameter = diameter; } }
为做到深复制,所有需要复制的对象都需要事先java.io.Serializable接口。这表明此类可以被安全地复制。
从运行结果可以看出,大圣的金箍棒和他的身外之身的金箍棒不是同一个对象,这是因为使用了深复制,从而把大圣本尊所引用的对象也都复制了一遍,其中也包括金箍棒。
什么时候使用原型模式
对于产品结构可能会有经常性变化的系统来说,采用工厂模式就有不方便之处。这时如果采取原始模型模式,给每一个产品类配备一个克隆方法(通常情况下只需给产品类登记结构的根类配备一个克隆方法),便可以避免使用工厂模式所带来的具有等级结构的工厂类。
这样,一个使用了原始模型模式的系统与它的产品对象是怎么创建出来的,以及这些产品对象之间的结构式怎样的,以及这个结构会不会发生变化是没有关系的。
原始模型的优点:
1.原始模型模式允许动态地增加或减少产品类。由于创建产品类实例的方法是产品类内部具有的,因此,增加新产品对整个结构没有影响。
2.原始模型模式提供简化的创建结构。java语法本身就支持。
3.具有给一个应用软件动态加载新功能的能力。如果系统的某个部分不再符合原有的要求,那么只要提供一个这个类的克隆,并在客户端登记即可,没有必要再给软件用户一个新的软件包。
4.产品类不需要非得有任何事先确定的等级结构,因为原始模型模式适用于任何等级结构。
原始模型模式最主要的缺点:
是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定能很容易,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
发表评论
-
设计模式记录(3.2)
2010-09-08 21:36 758第二十五章 合成模式 合成模型模式属于对象的结构模式, ... -
设计模式记录(3.1)
2010-09-08 13:16 716第四部分 结构模式 第二十二章 适配器模式 适配器模式 ... -
设计模式记录(2.3)
2010-09-03 23:20 767第十九章 建造模式 建造模式似乎对象的创建模式。建造模 ... -
设计模式记录(2.2)
2010-09-03 08:30 858第十五章 单例模式 单 ... -
设计模式记录(2.1)
2010-09-01 20:04 884第十二章 简单工厂模式 ... -
设计模式记录(1)
2010-08-31 19:05 903第四章 开闭原则 开闭原则讲的是:一个软件实体应该对扩展开放, ...
相关推荐
### 2.4GHz天线设计与微带平衡器实现:专为CC2430设计 #### 引言 在无线通信领域,天线设计是确保设备性能的关键环节之一。尤其是在2.4GHz频段,由于其广泛应用于蓝牙、Wi-Fi、Zigbee等无线技术,对天线的要求尤为...
书名: 设计模式可复用面向对象软件的基础 英文原书名: Design Patterns:Elements of Reusable Object-Oriented software 作者: Erich Gamma 等 译者: 李英军 马晓星 蔡敏 刘建中 书号: 7-111-07575-7 页码: 254 定价...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 引言 设计模式是软件工程领域中一个极为重要的概念,它代表着一系列被广泛接受的解决特定问题的方法。GoF(Gang of Four)所提出的23种设计模式更是...
我们并不认为这组设计模式是完整的和一成不变的,它只是我们目前对设计的思考的记录。因此我们欢迎广大读者的批评与指正,无论从书中采用的实例、参考,还是我们遗漏的已知应用,或应该包含的设计模式等方面。你...
设计模式的学习和应用分为几个阶段:首先自己学会设计模式,然后将其转化为自己的语言表达出来,接着是教授他人并最终记录下来。这种学习路径要求学习者不仅要理解设计模式本身,还要具备清晰的表达能力和深刻的理解...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 0. 引言 设计模式是软件工程领域的一个重要概念,它为解决特定问题提供了一套标准的解决方案。《设计模式精解——GoF 23种设计模式解析及C++实现源码...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 0. 引言 ##### 0.1 设计模式解析(总序) 设计模式是面向对象编程中用于解决常见问题的一系列模板。它们为软件设计提供了标准化的解决方案,帮助...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...
本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 ...
最出名的设计模式,语言诙谐明了。 目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...
### 设计模式精解—GoF 23种设计模式解析及C++实现源码 #### 0. 引言 设计模式作为一种重要的面向对象设计工具,在软件开发中扮演着至关重要的角色。本文旨在深入解析GoF(Gang of Four,四人组)提出的23种设计...
根据给定的信息,本文将深入探讨GoF23种设计模式的核心概念及其应用场景,并通过具体的实例来解析每一种设计模式的实现原理和技术要点。 ### 0. 引言 设计模式是一系列被广泛接受的解决方案,用于解决软件设计中...