定義
當創建給定實例的過程很複雜時,就應該使用原型模式(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;
}