原型(Prototype)模式
声明:
本节内容源自网络
【一句话介绍】
“原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节”
原型模式允许你通过复制现有的实例来创建新的实例(在Java中,这通常意味着使用clone()方法,或则反序列化Serilization)
当创建给定类的实例的过程很昂贵或很复杂的时候,就使用原型模式(Prototype Pattern)。
【先混个脸熟】
①类图
②简略图
③代码
抽象原型角色接口
public interface Prototype extends Cloneable{
Prototype clone();
}
具体原型角色实现了clone()方法
public class ConcretePrototype implements Prototype {
@Override
public Object clone() {
// TODO Auto-generated method stub
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
return null;
}
}
}
【应用场景】
- 在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时,可以考虑原型。
例如:
在不同的游戏场景中(沙漠、山谷、海洋),怪兽军团中各种怪兽的创建
【优点】
- 向客户隐藏制造新实例的复杂性
- 提供让客户能够产生未知类型对象的选项
- 原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。
- 某些环境下,复制对象比创建新对象更有效
【缺点】
- 对象的复制有时相当复杂
- 有一些对象,比如线程(thread)对象或Socket对象,是不能简单复制或共享的。不管是使用深复制还是浅复制,只要涉及这样的间接对象,就必须把间接对象设成 transient而不予复制
- 深度克隆时,必须要小心,全盘考虑,有的类可能会存在循环引用。
【小结-深复制和浅复制】
Cloneable接口
Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone()方法。通过调用这个clone()方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone()方法会抛出CloneNotSupportedException异常。
浅复制
浅复制仅仅复制原有对象的值,而不复制它所引用的对象。
浅复制显然比深复制更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正是浅复制。
深复制
深复制把要复制对象所引用的对象都复制了一遍,在深复制过程中,很可能出现循环引用的问题,必须小心处理。
利用串行化(Serilization)来做深复制
把对象写到流里的过程是串行化(Serilization)过程,但在Java程序师圈子里又非常形象的称为“冷冻”或“腌制(pickling)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者回鲜(depickling)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌制咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成一个咸菜),再从流里读回来(把咸菜回鲜),便可以重建对象。
深复制的代码示例;
public Object deepClone(){
//将对象写进流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读回来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectOutputStream oi = new ObjectOutputStream(bi);
return (oi.readObject());
}
有一些对象,比如线程(thread)对象或Socket对象,是不能简单复制或共享的。不管是使用深复制还是浅复制,只要涉及这样的间接对象,就必须把间接对象设成 transient而不予复制,或者由程序自行创建出相当的同种对象,权且当做复制件使用。
【讲个故事】
孙悟空拔猴毛变猴子猴孙的故事。
客户角色--------齐天大圣
猢狲实例----------Monkey类
金箍棒类---------GoldRingedStaff 被Monkey类所引用
齐天大圣持有猢狲实例,猢狲就是大圣本尊。Monkey类具有继承java.lang.Object的clone()方法,因此,可以通过调用这个克隆方法来复制一个Monkey实例。
浅复制的实例
以下代码仅给出简略代码
客户端角色
public class TheGreatestSage {
private Monkey monkey = new Monkey();
public void change(){
//克隆大圣本尊
Monkey copyMonkey = (Monkey)monkey.clone();
... ...
}
}
具体原型角色--Monkey类
public class Monkey implements Cloneable {
//身高
private int height;
//体重
private int weight;
//生日
private Date birthDate;
//金箍棒
private GoldRingedStaff staff;
/**
* 构造函数
*/
public Monkey(){
this.birthDate = new Date();
this.staff = new GoldRingedStaff();
}
/**
* 克隆方法
*/
public Object clone(){
Monkey temp = null;
try {
temp = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
return temp;
}
}
//省略
... ...
}
被引用的金箍棒类GoldRingedStaff
public class GoldRingedStaff {
private float height = 100.0f;
private float diameter = 10.0f;
/**
* 增长行为,每次调用长度和半径增加一倍
*/
public void grow(){
this.diameter *= 2;
this.height *= 2;
}
/**
* 缩小行为,每次调用长度和半径减少一半
*/
public void shrink(){
this.diameter /= 2;
this.height /= 2;
}
}
运行结果:
正如前面所述,继承自java.lang.Object类的clone()方法是浅克隆。换言之,齐天大圣的所有化身所持有的金箍棒引用全都是指向一个对象的,这与《西游记》中的描写并不一致。要纠正这一点,就需要考虑使用深克隆。
深度克隆的实例
所有需要复制的对象都需要实现java.io.Serializable接口
客户端角色TheGreatestSage类 与上同,略。
具体原型角色 大圣本尊--Monkey类,深度克隆方法deepClone()
public class Monkey implements Cloneable,Serializable {
。。。。。。
public Object deepClone() throws IOException, ClassNotFoundException{
//将对象写到流里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
。。。。。。
}
public class GoldRingedStaff implements Serializable{
。。。 。。。
}
大圣本尊持有一个金箍棒(GoldRingedStaff)的实例。在大圣复制件里面,此金箍棒实例是原大圣本尊对象所持有的金箍棒对象的一个拷贝。在大圣本尊对象被序列化和反序列化时,它所持有的金箍棒对象也同时被序列化和反序列化,这使得复制的大圣的金箍棒和原大圣本尊对象所持有的金箍棒对象是两个独立的对象。
运行结果:
从运行的结果可以看出,大圣的金箍棒和他的身外之身的金箍棒是不同的对象。这是因为使用了深克隆,从而把大圣本尊所引用的对象也都复制了一遍,其中也包括金箍棒。
【参考】
阎宏 Java与模式
【结束】
最后,猴子镇楼