孙悟空在与黄风怪的战斗中,使用了一个身外身的手段:把毫毛揪下一把,用口嚼和粉碎,往一喷叫声'变',变有百十个行者,都是一样的打扮!..老孙的这种身外身的手段在面向对象的设计领域里叫做原始模式.
一:克隆满足的条件.
A:对任何的对象x,都有:x.clone()!=x,换言之,克隆对象与原对象不是同一个对象.
B:对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原始对象的类型一样C:如果对象x的equals()方法定义恰当的话,那么x.clone.equals(x)应当是成立的.
在Java语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件,Java语言的设计师在设计自己的clone方法时也应当遵守这三个条件。一般来说,上面的三个条件的前两个是必需的,而第三个是可选的,但建议遵守上面三个条件.
疑问:clone是产生了一个新的对象,那直接用new关键字产生好了(但这时候的对象里的数据都是原始数据而已),clone有何重要作用.
附:equals()方法的讨论
上面所给出的三个条件中,第三个条件常常被忽视,很多读者误认为可以通过继承得到java.object.Object对象的equals()方法就足够了,而这是不对的,先看下java.lang.Object的源码:
public boolean equals(Object obj){
return (this==obj);
}
也就是说,当两个变量指向同一个对象时,equals方法才会返回true,很显然,这并不适合所有需要被克隆的对象,假设被克隆的对象按照它们的内部状态是否可变,划分为可变对象和不变对象(String就是不变对象,详情看不变模式一章)的话,那么可变对象和不变对象所提供的equals方法的工作方式应当是不同的.可变对象只有当它们是同一个对象时,equals才会返回true,所以这样的类型可以直接从java.lang.Object继承这个方法,不变对象必须含有相同的状态才可能满足这个条件,因此不变类型必须自行实现这个equals方法.一个典型的例子就是java的String对象,它是一个不变对象,而不变对象具有不会改变的内部状态,具有相同内部状态的String对象对于客户端就没有区别.这样一来,两个String对象的比较就应当是它们的内部状态的比较。
二:原始模型模式的结构
原始模型模式有两种表现形式,第一种是简单形式,第二种是登记形式.
A:简单形式的原始模型模式:这种形式涉及到三种角色:
(1)客户端(Client)角色:客户类提出创建对象的请求
(2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个java接口或java抽象类实现,此角色给出所有的具体原型所需的接口
(3)具体原型(Concrete Prototype)角色:被复制的对象,此角色需要实现抽象的原型角色里要求的接口.
下面是示意源码:
package cai.milenfan.basic.test;
public class Client {
private Prototype prototype;
public void operation(Prototype example){
Prototype p = (Prototype)example.clone();
}
}
package cai.milenfan.basic.test;
public interface Prototype extends Cloneable{
Prototype clone();
}
package cai.milenfan.basic.test;
public class ConcretePrototype implements Prototype{
public Prototype clone(){
try {
return (Prototype)super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
B:登记形式的原始模型模式
它有如下角色:
(1)客户端(Client)角色:客户端类向管理员提出创建对象的请求
(2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体原型类所需要的接口.
(3)具体原型(Concrete Prototype)角色:被复制的对象,需要实现抽象的原型角色所要求的接口
(4)原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象.
下面给出源码:
package cai.milenfan.basic.test;
public interface Prototype extends Cloneable{
public Object clone();
}
package cai.milenfan.basic.test;
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 cai.milenfan.basic.test;
import java.util.Vector;
public class PrototypeManager {
private Vector objects = new Vector();
public void add(Prototype object){
objects.add(object);
}
public Prototype get(int i){
return (Prototype)objects.get(i);
}
public int getSize(){
return objects.size();
}
}
package cai.milenfan.basic.test;
public class Client {
private PrototypeManager mgr;
private Prototype prototype;
public void registerPrototype(){
prototype = new ConcretePrototype();
Prototype copytype = (Prototype)prototype.clone();
mgr.add(copytype);
}
}
两种形式的比较:
如果需要创建的原型对象数目较少而且比较固定的话,可以采取第一种形式,这种情况下原型对象由客户端自己保存.如果要创建的原型对象数目不固定的话可以采取第二种形式,这种情况下,客户端并不保存原型对象的引用,这个任务被交给管理员对象,在复制一个原型对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象,如果有,可以直接从管理员类取得这个对象的引用,如果没有,客户端就需要自行复制此原型对象.
三:模式的实现:深复制和浅复制
复制或克隆有两种方式,这两种方式分别叫做浅复制(浅克隆)和深复制(深克隆)
(1)浅复制(浅克隆)
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象,换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象
(2)深复制(深克隆)
被复制的对象所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新的对象,而不是原有的那些被引用的对象,换言之,深复制把复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。深复制要深入到多少层,是一个不易确定的问题,在决定以深复制的方式复制一个对象的时候,必须决定对间接复制的对象是采取浅复制还是继续采用深复制,因此,在采取深复制时,需要决定多深才算深,此外,在深复制过程中,很可能会出现循环引用的问题,必须小心处理.
四:利用串行化来做深复制
把对象写到流里的过程是串行化(Serilization)过程,也被称为"冷冻"过程,而把对象从流中读出来的并行化过程则叫做"解冻"过程,应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于jvm里面.
在java语言里面深复制一个对象,常常可以先使其实现Serializable接口,然后把对象(实际只是对象的一个拷贝)写到一个流里,然后再从流里读出来,代码如下:
package cai.milenfan.basic.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyDeepClone {
public Object deepClone() throws IOException, ClassNotFoundException{
//将对象写对流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return(oi.readObject());
}
}
以上做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则就需要仔细考察那些不可串行化的对象可否transient,从而将之排除在复制过程之外.
浅复制显然比深复制更容易实现,因为java语言里的所有类都继承了clone方法,而这个clone方法所做的正是浅复制.有一些对象比如线程(thread)对象或Socket对象,是不能简单复制或共享的,不管使用深复制还是浅复制,只要涉及这样的间接对象,就必须把间接对象设成transient而不给复制.
五:孙悟空的变身术
TheGreatestSage类扮演客户角色,Monkey是大圣,Monkey用Object的clone方法进行浅复制的代码如下:
package cai.milenfan.basic.test;
public class GoldRingedStaff {
private float height = 100.0f;
private float diameter = 10.0f;//半径
public void grow(){
this.height *= 2;
this.diameter *= 2;
}
public void shrink(){
this.height /= 2;
this.diameter /= 2;
}
public void move(){}
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;
}
}
package cai.milenfan.basic.test;
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();
}
public Object clone(){
Monkey temp = null;
try {
temp = (Monkey)super.clone();
return temp;
} 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 GoldRingedStaff getStaff() {
return staff;
}
public void setStaff(GoldRingedStaff staff) {
this.staff = staff;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
}
package cai.milenfan.basic.test;
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 birthdate = " monkey.getBirthDate());
System.out.println("Copy monkey's birthdate = " 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 King's birthdate = Fri Aug 14 12:55:50 CST 2009
Copy monkey's birthdate = Fri Aug 14 12:55:50 CST 2009
Monkey King == Copy Monkey?false
Monkey King's Staff==Copy Monkey's Staff?true
可以看出,首先,复制的大圣本尊具有和原始的大圣本尊对象一样的birthDate,而本尊对象并不相等,这表示他们二者是克隆关系,其次,复制的大圣本尊所持有的金刚棒和原始的大圣本尊所持有的金刚棒是一样的,而不是两根...这和电视剧里的并不太一样,要纠正这点,就需要用深复制。
深复制的源码如下:
package cai.milenfan.basic.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
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, ClassNotFoundException{
//将对象写对流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
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();
return temp;
} 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 GoldRingedStaff getStaff() {
return staff;
}
public void setStaff(GoldRingedStaff staff) {
this.staff = staff;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
}
package cai.milenfan.basic.test;
import java.io.Serializable;
public class GoldRingedStaff implements Cloneable,Serializable{
private float height = 100.0f;
private float diameter = 10.0f;//半径
public void grow(){
this.height *= 2;
this.diameter *= 2;
}
public void shrink(){
this.height /= 2;
this.diameter /= 2;
}
public void move(){}
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;
}
}
package cai.milenfan.basic.test;
import java.io.IOException;
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 birthdate = " monkey.getBirthDate());
System.out.println("Copy monkey's birthdate = " 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 King's birthdate = Fri Aug 14 14:13:50 CST 2009
Copy monkey's birthdate = Fri Aug 14 14:13:50 CST 2009
Monkey King == Copy Monkey?false
Monkey King's Staff==Copy Monkey's Staff?false
从运行的结果看出,大圣的金刚棒和他本身都是不同的对象,这是因为使用了深复制,从而把大圣本尊所引用的对象也都复制了一遍.
六:在什么情况下使用原始模型
假设一个系统的产品类是动态加载的,而且产品类具有一定的等级结构,这个时候如果采取工厂模式的话,工厂类就不得不具有一个相应的等级结构,而产品类的等级结构一旦变化,工厂类的等级结构就不得不有一个相应的变化,这对于产品结构可能会有经常性的变化的系统来说,采用工厂模式就有不方便之处,这时如果采取原始模型模式,给每一个产品类配备一个克隆的方法(大多数的时候只需要给产品类等级结构的根类配备一个克隆的方法),便可以避免使用工厂模式带来的具有固定等级结构的工厂类。这样,一个使用了原始模型模式的系统与它的产品对象是怎么创建出来的,以及这些产品对象之间的结构是怎么样的,以及这个结构会不会发生变化是没有关系的。
????????-->不明白
七:原始模型模式的优点和缺点
抽象工厂模式有许多与原始模型模式和建造模式相同的效果,包括客户端不知道具体的产品类,而只知道抽象产品类,客户端不需要知道这么多的具体产品名称,如果有新的产品类加入,客户端不需要进行改造就可以直接使用.
原始模型模式有其特有的优点:
(1)原始模型模式允许动态地增加或减少产品类,由于创建产品类实例的方法是产品类内部具有的,因此,增加新产品对整个结构没有影响。
(2)原始模型模式提供简化的创建结构,工厂方法模式常常需要一个与产品类等级结构相同的等级结构,而原始模型模式就不需要这样.???
(3)具有给一个应用软件动态加载新功能的能力.
(4)产品类不需要非得有任何事先确定的等级结构,因为原始模型模式适用于任何等级结构.
原始模型模式的缺点主要是每一个类都必须配备有一个克隆的方法,配备克隆的方法需要对类的功能进行通盘的考虑,这么于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持串行化的间接对象或者引用含有循环结构的时候.