定义
当创建给定实例的过程很复杂时,就应该使用原型模式(Prototype)。
利用原型实例指定要创建对象的种类,并且通过拷贝这些原型实例,以创建新的对象。
原型模式属于创建型模式。
要点
原型模式允许通过复制现有的实例来创建新的实例,在Java中,这通常意味着使用 clone() 方法或反序列化。
- 向客户隐藏制造新实例的复杂性。
- 提供让客户能够产生未知类型对象的选项。
- 在某些环境下,复制对象比创建对象更有效。
- Prototype: 原型类,声明一个克隆自己的接口
- ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
- Client: 让一个原型对象克隆自己,从而创建一个新的对象
场景
为了快速构造一个和已有对象相同的副本。 在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时,可以考虑原型。
例如,克隆羊问题,现在有一只羊实例,请创建和该羊属性完全相同的 3 只羊。
public class Client {
public static void main(String[] args) {
// 传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
}
}
这种传统的方式简单易操作,但是:
- 创建新对象时,需要重新获取原始对象的属性,若原始对象相对复杂时,效率较低。
- 需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活。
实现
/**
* 羊,实现了Cloneable接口
*/
public class Sheep implements Cloneable {
/**
* 羊的名字
*/
private String name;
/**
* 羊的年龄
*/
private int age;
/**
* 羊的颜色
*/
private String color;
/**
* 该羊的母亲 (clone默认是浅拷贝)
*/
private Sheep mother;
public Sheep(String name, int age, String color, Sheep mother) {
this.name = name;
this.age = age;
this.color = color;
this.mother = mother;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getColor() {
return color;
}
public Sheep getMother() {
return mother;
}
@Override
public String toString() {
return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", mother's hashcode = " + mother.hashCode() + "]";
}
/**
* 克隆该实例,使用默认的 clone 方法来完成
*/
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
Client
/**
* 使用原型模式创建新对象
*/
public class Client {
public static void main(String[] args) {
System.out.println("原型模式完成新对象的创建 --> \n");
// 原始羊(被克隆的羊)
Sheep sheep = new Sheep("羊羔", 1, "白色", new Sheep("母亲", 3, "黑白色", null));
// 克隆羊
Sheep cloneSheep1 = (Sheep) sheep.clone();
Sheep cloneSheep2 = (Sheep) sheep.clone();
// 原始羊信息
System.out.println("原始羊 : " + sheep);
// 克隆羊信息
System.out.println("克隆羊1 : " + cloneSheep1);
System.out.println("克隆羊2 : " + cloneSheep2);
}
}
源代码
总结
- 关于原型模式
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
不用重新初始化对象,而是动态地获得对象运行时的状态
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
在实现深克隆的时候可能需要比较复杂的代码
缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,这点请注意.
- clone 默认使用的是浅拷贝(shadow clone)
若实例域是基本数据类型,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
若实例域是引用数据类型,例如某个数组、某个类对象等,那么浅拷贝只会进行引用传递,只是将该成员变量的引用值(内存地址)复制一份给新的对象,实际上使用的是相同的成员变量对象。
应用:Spring 中原型 bean 的创建,就是原型模式的应用
实现深拷贝的方法(deep clone)
- 使用对象序列化来实现深拷贝(推荐)
- 重写 clone 方法实现深拷贝
序列化实现
传入的对象需要实现 Serializable 接口
/**
* 深拷贝一个对象
*/
public Object deepClone(Object src) {
Object object = null;
try {
if (src != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(src);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
object = ois.readObject();
ois.close();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return object;
}
重写clone实现
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
sheep.mother = this.mother == null ? null : (Sheep) this.mother.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}