描述
原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
简介
原型模式类图原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
角色
- 客户(Client)角色:客户类提出创建对象的请求。
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
优缺点
优点
- 当创建对象的实例较为复杂的时候,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高实例的创建效率。
- 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
- 原型模式的每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,且修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
使用场景
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
克隆
克隆满足的条件
- 对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。(必须)
- 对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。(必须)
- 如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。(可选)
浅克隆和深克隆
- 浅克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
- 深克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
示例
为了更好的理解原型模式以及浅克隆与深克隆的区别,下面我们用西游记中孙悟空的分身术来做讲解。
浅克隆
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;
}
}
public class Monkey implements Cloneable {
private int height;
private int weight;
private Date birthDate;
private GoldRingedStaff staff;
@Override
protected Object clone() throws CloneNotSupportedException {
Monkey temp = null;
try {
temp = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 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;
}
public void setStaff(GoldRingedStaff staff) {
this.staff = staff;
}
@Override
public String toString() {
return "Monkey{" +
"height=" + height +
", weight=" + weight +
", birthDate=" + birthDate +
", staff=" + staff +
'}';
}
}
public class Client {
public static void main(String[] args) {
Monkey monkey = new Monkey();
monkey.setHeight(170);
monkey.setWeight(100);
monkey.setBirthDate(new Date());
monkey.setStaff(new GoldRingedStaff());
try {
Monkey cloneMonkey = (Monkey) monkey.clone();
System.out.println("大圣本尊的生日是:" + monkey.getBirthDate());
System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate());
System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == cloneMonkey));
System.out.println("大圣本尊持有的金箍棒跟克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == cloneMonkey.getStaff()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
程序输出结果:
浅克隆结果
可以看出,首先,复制的大圣本尊具有和原始的大圣本尊对象一样的birthDate,而本尊对象不相等,这表明他们二者是克隆关系;其次,复制的大圣本尊所持有的金箍棒和原始的大圣本尊所持有的金箍棒为同一个对象。这表明二者所持有的金箍棒根本是一根,而不是两根。
深克隆
public class GoldRingedStaff implements Serializable{
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;
}
}
public class Monkey implements Cloneable, Serializable {
private int height;
private int weight;
private Date birthDate;
private GoldRingedStaff staff;
@Override
protected Object clone() throws CloneNotSupportedException {
Monkey temp = null;
try {
temp = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
return temp;
}
}
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 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;
}
public void setStaff(GoldRingedStaff staff) {
this.staff = staff;
}
@Override
public String toString() {
return "Monkey{" +
"height=" + height +
", weight=" + weight +
", birthDate=" + birthDate +
", staff=" + staff +
'}';
}
}
public class Client {
public static void main(String[] args) {
Monkey monkey = new Monkey();
monkey.setHeight(170);
monkey.setWeight(100);
monkey.setBirthDate(new Date());
monkey.setStaff(new GoldRingedStaff());
try {
Monkey cloneMonkey = (Monkey) monkey.deepClone();
System.out.println("大圣本尊的生日是:" + monkey.getBirthDate());
System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate());
System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == cloneMonkey));
System.out.println("大圣本尊持有的金箍棒跟克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == cloneMonkey.getStaff()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
程序输出结果:
深克隆结果
从运行的结果可以看出,大圣的金箍棒和他的身外之身的金箍棒是不同的对象。这是因为使用了深克隆,从而把大圣本尊所引用的对象也都复制了一遍,其中也包括金箍棒。
网友评论