什麼是原型模式
聽說過克隆技術吧?再通俗點,就是複製。
原型模式就是代碼裏面的克隆技術,把一個已經實例化的對象當作原型,克隆出一個跟它相同的對象。
可以採用new
方式構造一個新對象,再把原型對象的內部狀態拷貝過來,達到複製原型對象的目的,但是這就涉及關心到了原型內部,沒有克隆模式來的簡單直接。
原型模式的實現思路
從是否具備克隆能力的角度看,有兩種實現思路:
- 原型自身具備克隆能力,就像孫悟空,拔根猴毛就能變出一個分身
- 原型自身不具備克隆能力,就像克隆羊多利,是生物學家掌握克隆技術,從母體身上克隆出來的
從Java
代碼實現角度看,有三種實現方案:
- 利用
Object
類提供的clone
方法實現,有淺克隆、深克隆兩種模式 - 利用對象序列化機制實現,只有深克隆模式
- 還有一種方法就當發散思維了,那就是直接
new
新對象,再將原型模式屬性都複製過來(實際不這麼幹,也不推薦)
clone()
因爲Object
類裏面提供有clone
方法,而Object
類又是所有類的隱形基類,所以,通過繼承的途徑,每個對象就天然擁有自己克隆自己的潛力。
但是這個clone()
只能做到淺克隆模式,要做到深克隆還需要自己動手多做點事情。
序列化
序列化就是把內存中的對象信息存儲起來,需要的時候,再通過反序列化將儲存信息轉化成內存中的對象信息。
這句話有兩個要點:
- 序列化是轉換內存對象信息的過程,不包括對象的內存地址信息
- 反序列化是根據對象信息重建內存對象的過程
因此,這種方法只能實現深克隆模式,不能實現淺克隆模式。
原型模式的實現代碼
淺克隆
淺克隆的意思是:克隆出來的對象,所有“基本類型變量值”和“引用類型變量值”都和原型對象一樣。
也就是說,如果原型對象中有一個屬性指向對象A
,那麼克隆對象相同的屬性也指向對象A
。
淺克隆只複製原型對象、以及原型對象屬性的基本類型值和引用值,不會複製內部屬性引用的其它對象。
代碼實現分兩步走:
- 原型類實現
java.lang.Cloneable
接口,這是一個空的標記接口,並無實際作用,只是遵循Java
的編程規範,表明這類對象可以克隆。 - 原型類重寫
Object
類中的clone
方法,並將其訪問修飾符變更爲public
,重寫邏輯裏面需要調用Object
的clone
方法,也就是super.clone()
。
代碼實現:
public class Teacher {
}
// 原型
public class Student implements Cloneable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
// 測試
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student(new Teacher());// 原型對象
Student clone = student.clone();// 克隆對象
System.out.println(student.getTeacher() == clone.getTeacher());// true
}
深克隆
深克隆的意思是:克隆出來的對象,所有“基本類型變量值”和原型對象相同,“引用類型變量值”和原型對象不同。
也就是說,如果原型對象中有一個屬性指向對象A
,那麼,對象A
也要被克隆,最後,克隆對象相同的屬性不是指向A
對象,而是指向新對象A'
。
深克隆不僅複製原型對象,還會複製原型對象內部屬性的引用對象,克隆的特別透徹。
實現深克隆,就在淺克隆基礎上多做一點事情:手動把原型所有的引用屬性再克隆一次。
前面說了,深克隆有兩種方法:clone ()
、序列化。
第一種clone()
,代碼實現:
public class Teacher implements Cloneable {
@Override
protected Teacher clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
}
// 原型
public class Student implements Cloneable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
@Override
public Student clone() throws CloneNotSupportedException {
Student clone = (Student) super.clone();
clone.teacher = teacher.clone(); // 手動賦值,深克隆需要自己編碼多做事情
return clone;
}
}
// 測試
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student(new Teacher());// 原型對象
Student clone = student.clone();// 克隆對象
System.out.println(student.getTeacher() == clone.getTeacher());// false
}
第二種序列化,需要注意一點,通過這種方式進行的深克隆,相關類都需要實現java.io.Serializable
接口,這是Java
序列化機制要求的。代碼實現:
public class Teacher implements Serializable {
}
// 原型
public class Student implements Serializable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
// 因爲沒有利用Object的clone方法,所以,最好也別重寫clone方法,新定義一個方法比較好
public Student 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 (Student) ois.readObject();
}
}
// 測試
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(new Teacher());
Student clone = student.deepClone();
System.out.println(student.getTeacher() == clone.getTeacher());
}
原型模式的優缺點
優點
一旦構建好原型模式的克隆邏輯,就可以很方便的得到一個和原型一摸一樣的對象,不需要關心原型的內部信息。
缺點
需要理解淺克隆和深克隆的含義,不能在這上面犯錯。
克隆邏輯往往具有全局影響力,設計的時候需要全盤考量,對原來的克隆邏輯進行改動更是要慎之又慎!
當對象引用屬性包含着循環結構的時候,特別容易出問題!
採用序列化機制的時候,還要考慮對象引用屬性是否都能繼承Serializable
接口,如果屬性對象是第三方jar
包裏的,很可能就會出現不支持序列化的問題,要慎重!