原始模型(Prototype):
属于对象的创建模式。通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
java.lang.Object.clone()方法,所有javaBean都必须实现Cloneable接口,才具有clone功能;
Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知java虚拟机可以安全地在这个类上使用clone()方法。通过调用
这个clone()方法可以得到一个对象的复制。由于Object类本身并实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接
口时,调用clone()方法会抛出CloneNotSupportedException异常。
二、克隆满足的条件:
1、clone()方法将对象复制了一份并返还给调用者。一般而言,克隆方法满足以下条件:
1):对任何的对象x,都有:x.clone() != x。换言之,克隆对象与原始对象不是同一个对象。
2):对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原始对象的类型一样。
3):如果对象x的equals()方法定义恰当的话,那么x.clone().equals(x)应当是成立的。
三、原始模型模式的结构;
1、简单形式的原始模型模式
1):这种形式涉及到三个角色:
。 客户(Client)角色:客户类提出创建对象的请求。
。 抽象原始(Prototype)角色:这是一个抽象角色,通常由一个java接口或java抽象类实现。此角色给出
所有的具体原始类所需要的接口。
。 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
3、两种形式的比较
简单形式和登记形式的原始模型模式各有其长处和短处。
1)如果需要创建的原型对象数目较少而且比较固定的话,可以采取第一种形式,也即简单形式的原始模型模式。
在这种情况下,原型对象的引用可以由客户端自己保存。
2)如果要创建的原型对象数目不固定的话,可以采取第二种形式,也即登记形式的原始模型模式。
在这种情况下,客户端并不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个原型对象之前,客户端
可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象引用;如果没有,
客户端就需要自行复制此原型对象。
4、模式的实现
1):浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象,换言之,
浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
2):深复制(深克隆)
被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将
指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了
一遍,而这种对被引用到的对象的复制叫做间接复制。
3):利用串行化来作深复制
把对象写到流里的过程是串行化(Serilization)过程,写到流里的是对象的一个拷贝,而原来对象仍然存在于JVM
里面,因此“腌成咸菜”(串行化)的只是对象的一个拷贝,java咸菜(并行化Deserialization)还可以回鲜。
在java语言里深复制一个对象,常常可以使一个对象实现Serialization接口,然后把对象(实际上只是对象的一个拷贝)
写到一个流里(腌成咸菜),再成流里读回来(把咸菜回鲜),便可以重建对象。
//深复制
public Object deepClone(){
//将对象写到流里
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,从而将之排除在复制过程之外。
5、模式的选择
1):如果客户角色所使用的原型对象只有固定的几个,那么使用单独的变量来引用每一个原型对象就很方便,
这时候简单形式的原始模型模式就比较合适。
2):而如果客户端所创建的原型对象的个数不是固定的,而是动态地变化的,那么就不妨使用一个Vector类型的变量
用来动态地存储和释放原型对象。这时候,使用第二中形式的原始模型模式就比较合乎需要。
6、在什么情况下使用原始模型模式
假设一个系统的产品类是动态加载的,而且产品类有一定的等级结构。
这是采取原始模型模式,给每一个产品类配备一个克隆方法(大多数的时候只需要给产品类等级结构的根类配备一个克隆方法)
便可以避免使用工厂模式所带来的具有固定等级结构的工厂类。
7、原始模型模式的优点与缺点
1):特有的优点
/:原始模型模式原许动态地增加或减少产品类。由于创建产品类实例的方法是产品类内部具有的,因此,增加新产品对
整个结构没有影响。
/:原始模型模式提供简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,
而原始模型模式就不需要这样。
/:具有给一个应用软件动态加载新功能的能力。例如,一个分析Web服务器的记录文件的应用软件,针对每一种记录文件
格式,都可以由一个相应的“格式类”负责。如果出现了应用软件所不支持的新的Web服务器,只需要提供一个格式类
的克隆,并在客户端登记即可,而不必给每个软件的用户提供一个全新的软件包。
/:产品类不需要非得有任何事先确定的等级结构,因为原始模型模式适合用于任何的等级结构。
2):主要缺点
/:每一个类都必须配备一个克隆方法,配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,
而对于已经有的类不一定很容易,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
8、原始模型模式与其他模式的关系
1)原始模型模式与合成模式的关系
原始模型模式经常与合成模式一同使用,因为原型对象经常是合成对象。
2)原始模型模式与抽象工厂模式的区别
如果系统不需要动态地改变原型对象,抽象工厂模式可以成为原始模型模式的替代品
3)原始模型模式与门面(Facade)模式的区别
原型模型模式的客户端通常可以将系统的其他对象与参与原始模型模式的对象分割开,起到一个门面对象的作用。
4)原始模型模式与工厂犯法尬,模式的关系
如果原型对象只有一种,而且不会增加的话,工厂方法模式可以成为一种替代模式。
5)原始模型模式与装饰(Decorator)模式的关系
原始模型模式常常与装饰模式一同使用。
属于对象的创建模式。通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
java.lang.Object.clone()方法,所有javaBean都必须实现Cloneable接口,才具有clone功能;
Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知java虚拟机可以安全地在这个类上使用clone()方法。通过调用
这个clone()方法可以得到一个对象的复制。由于Object类本身并实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接
口时,调用clone()方法会抛出CloneNotSupportedException异常。
//PandaToClone类
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 Object clone(){
//创建一个本类对象并返回给调用者
PandaToClone temp = new PandaToClone(height,weight);
temp.setAge(age);
return (Object)temp;
}
}
//客户端
public class Client{
private static PandaToClone thisPanda, thatPanda;
public static void main(String args[]){
thisPanda = new PandaToClone(15,25);
thisPanda.setAge(3);
thatPanda = (PandaToClone)thisPanda.clone();
System.out.println("Age of this panda :" + thisPanda.getAge());
System.out.println("Height : " + thisPanda.getHeight());
System.out.println("Weigth : " + this.Panda.getWeight());
System.out.println("Age of that panda :" + thatPanda.getAge());
System.out.println("Height : " + thatPanda.getHeight());
System.out.println("Weigth : " + that.Panda.getWeight());
}
}
二、克隆满足的条件:
1、clone()方法将对象复制了一份并返还给调用者。一般而言,克隆方法满足以下条件:
1):对任何的对象x,都有:x.clone() != x。换言之,克隆对象与原始对象不是同一个对象。
2):对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原始对象的类型一样。
3):如果对象x的equals()方法定义恰当的话,那么x.clone().equals(x)应当是成立的。
三、原始模型模式的结构;
1、简单形式的原始模型模式
1):这种形式涉及到三个角色:
。 客户(Client)角色:客户类提出创建对象的请求。
。 抽象原始(Prototype)角色:这是一个抽象角色,通常由一个java接口或java抽象类实现。此角色给出
所有的具体原始类所需要的接口。
。 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
//客户端
public class Client{
private Prototype prototype;
public void operation(Prototype example){
Prototype p = (Prototype)example.clone();
}
}
//抽象原型角色
public interface Prototype implements Cloneable{
Prototype clone();
}
//具体原型角色
public class ConcretePrototype implements Prototype{
//克隆方法
public Object clone(){
try{
return super.clone();
}catch(CloneNotSupportedException e){
return null;
}
}
}
2、登记形式的原始模型模式
1):有如下角色:
。 客户端(Client)角色:客户端类向管理员提出创建对象的请求。
。 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个接口或抽象类实现。
此角色给出所有的具体原型类所需要的接口。
。 具体原型(Concrete Prototype)角色:被复制的对象。需要实现抽象原型角色所要求的接口。
。 原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象。
//抽象原型角色
public interface Prototype inplements Cloneable{
public Object clone();
}
//具体原型角色
public class ConcretePrototype implements Prototype{
//克隆方法
public synchronizer Object clone(){
Prototype temp = null;
try{
temp = (Prototype)super.clone();
return (Object)temp;
}catch(CloneNotSupportedException e){
System.out.println("Clone failed.");
}finally{
return (Object)temp;
}
}
}
//原型管理器角色保持一个聚集,作为多所有原型对象的登记,这个惧色提供必要的方法,
供外界增加新的原型对象和取得已经登记过的原型对象。
//原型管理器类
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();
}
}
//客户端
public class Client{
private PrototypeManager mgr;
private Prototype prototype;
public void registerPrototype(){
prototype = new ConcretePrototype();
Prototype copytype = (Prototype)prototype.clone();
mgr.add(copytype);
}
}
3、两种形式的比较
简单形式和登记形式的原始模型模式各有其长处和短处。
1)如果需要创建的原型对象数目较少而且比较固定的话,可以采取第一种形式,也即简单形式的原始模型模式。
在这种情况下,原型对象的引用可以由客户端自己保存。
2)如果要创建的原型对象数目不固定的话,可以采取第二种形式,也即登记形式的原始模型模式。
在这种情况下,客户端并不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个原型对象之前,客户端
可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象引用;如果没有,
客户端就需要自行复制此原型对象。
4、模式的实现
1):浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象,换言之,
浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
2):深复制(深克隆)
被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将
指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了
一遍,而这种对被引用到的对象的复制叫做间接复制。
3):利用串行化来作深复制
把对象写到流里的过程是串行化(Serilization)过程,写到流里的是对象的一个拷贝,而原来对象仍然存在于JVM
里面,因此“腌成咸菜”(串行化)的只是对象的一个拷贝,java咸菜(并行化Deserialization)还可以回鲜。
在java语言里深复制一个对象,常常可以使一个对象实现Serialization接口,然后把对象(实际上只是对象的一个拷贝)
写到一个流里(腌成咸菜),再成流里读回来(把咸菜回鲜),便可以重建对象。
//深复制
public Object deepClone(){
//将对象写到流里
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,从而将之排除在复制过程之外。
5、模式的选择
1):如果客户角色所使用的原型对象只有固定的几个,那么使用单独的变量来引用每一个原型对象就很方便,
这时候简单形式的原始模型模式就比较合适。
2):而如果客户端所创建的原型对象的个数不是固定的,而是动态地变化的,那么就不妨使用一个Vector类型的变量
用来动态地存储和释放原型对象。这时候,使用第二中形式的原始模型模式就比较合乎需要。
//孙大圣本人类
public class TheGreatestSage{ //GreatestSage(大圣)
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();
}
}
////////////////
import java.util.Date;
public class Monkey inplements Colenable{
private int height;
private int weight;
private GoldRingedStaff staff;
private Date birthDate;
public Monkey(){
this.birhtDate = new Date();
}
public Object clone(){
Monkey temp = null;
try{
temp = (Monkey)super.clone();
}catch(CloneNotSupportedException e){
System.out.println("Clone failed.");
}finally{
return (Object)temp;
}
}
public int getHeight(){
return height;
}
public void setHeight(int height){
this.height = height;
}
//其他重复
//生日的取值方法
public Date getBirthDate(){
return birthDate;
}
public void setBirthDate(Date birhtDate){
this.birthDate = birhtDate;
}
}
//金箍棒类
public class GoldRingedStaff{
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 you code
}
public float getHeight(){
return height;
}
public void setHeight(float heigth){
this.height = height;
}
//半径的取值。赋值方法。。。。。。。。
//深复制,必须要实现serializable接口
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();
}
}
//金箍棒类
import java.util.Date;
import java.io.Serializable;
public class GoleRingedStaff implements Cloneable,Serializable{
private float height = 100.0F;
private float diameter = 10.0F;
public GoldRingedStaff(){
//write ..............
}
//跟上一样
}
6、在什么情况下使用原始模型模式
假设一个系统的产品类是动态加载的,而且产品类有一定的等级结构。
这是采取原始模型模式,给每一个产品类配备一个克隆方法(大多数的时候只需要给产品类等级结构的根类配备一个克隆方法)
便可以避免使用工厂模式所带来的具有固定等级结构的工厂类。
7、原始模型模式的优点与缺点
1):特有的优点
/:原始模型模式原许动态地增加或减少产品类。由于创建产品类实例的方法是产品类内部具有的,因此,增加新产品对
整个结构没有影响。
/:原始模型模式提供简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,
而原始模型模式就不需要这样。
/:具有给一个应用软件动态加载新功能的能力。例如,一个分析Web服务器的记录文件的应用软件,针对每一种记录文件
格式,都可以由一个相应的“格式类”负责。如果出现了应用软件所不支持的新的Web服务器,只需要提供一个格式类
的克隆,并在客户端登记即可,而不必给每个软件的用户提供一个全新的软件包。
/:产品类不需要非得有任何事先确定的等级结构,因为原始模型模式适合用于任何的等级结构。
2):主要缺点
/:每一个类都必须配备一个克隆方法,配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,
而对于已经有的类不一定很容易,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
8、原始模型模式与其他模式的关系
1)原始模型模式与合成模式的关系
原始模型模式经常与合成模式一同使用,因为原型对象经常是合成对象。
2)原始模型模式与抽象工厂模式的区别
如果系统不需要动态地改变原型对象,抽象工厂模式可以成为原始模型模式的替代品
3)原始模型模式与门面(Facade)模式的区别
原型模型模式的客户端通常可以将系统的其他对象与参与原始模型模式的对象分割开,起到一个门面对象的作用。
4)原始模型模式与工厂犯法尬,模式的关系
如果原型对象只有一种,而且不会增加的话,工厂方法模式可以成为一种替代模式。
5)原始模型模式与装饰(Decorator)模式的关系
原始模型模式常常与装饰模式一同使用。