原型模式
1.克隆羊問題
(1) 問題
一隻羊,姓名爲Tom,年齡爲1,顏色爲白色。現在需要創建10只羊,他們的屬性和Tom的屬性完全相同。
(2) 傳統方法實現
/** 羊類 **/
public class Sheep{
private String name;
private int age;
private String color;
public Sheep(String name, int age, String color){
super();
this.name = name;
this.age = age;
this.color = color;
}
setter & getters
}
/** 創建羊 **/
public class Client(){
psvm() {
Sheep tom = new Sheep("Tom",1,"白色");
Sheep sheep2 = new Sheep(tom.getName(), tom.getAge(), tom.getColor());
Sheep sheep3 = new Sheep(tom.getName(), tom.getAge(), tom.getColor());
...10次
}
}
(3) 優缺點
- 優點:簡單好理解
- 缺點:在創建新的對象時,總是需要重新獲取原始對象的屬性(tom.getXX()),如果創建的對象比較複雜時,效率較低。
- 總是需要重新初始化對象,而不是動態獲取的對象運行時的狀態,不夠靈活。
(4) 解決方案
Java中Object類是所有類的根類,Object類提供了一個clone()方法,該方法可以將一個Java對象複製一份。
但是需要實現clone的Java類必須要實現一個接口Cloneable,該接口表示該類能夠複製且具有複製的能力。
2.原型模式(Prototype)
(1) 基本介紹
- 用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象。
- 原型模式是一種創建型設計模式,允許一個對象在創建另一個可定製的對象,無需知道如何創建的細節。
- 通過一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝他們自己來實施創建。即對象.clone()
- 原理:
(2) 用原型模式實現克隆羊
/** 羊類 **/
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
public Sheep(String name, int age, String color){
super();
this.name = name;
this.age = age;
this.color = color;
}
// 克隆該實例
@override
protected Object clone() throws CloneNotSupportedException{
Sheep sheep = null;
sheep = (Sheep)super.clone(); // 使用默認的clone方法完成
return sheep ;
}
setter & getters
}
/** 創建羊 **/
public class Client(){
psvm() {
Sheep tom = new Sheep("Tom",1,"白色");
Sheep sheep2 = (Sheep)tom.clone();
Sheep sheep3 = (Sheep)tom.clone();
}
}
(3) 優缺點
- 優點:這樣如果羊新增一個屬性,比如地區,只需要改動羊類的屬性和Tom的實例創建代碼即可。
- 缺點:淺拷貝
如果羊Sheep對象中有一個屬性:private Sheep friend; 使用默認的clone()這個拷貝前後這個屬性指向同一實例對象。
3.淺拷貝和深拷貝
(1) 淺拷貝
- 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。
- 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,淺拷貝會進行引用傳遞:只是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的該成員變量都指向同一個實例。在拷貝前的對象中修改成員變量,會影響到拷貝後的對象中的該成員變量的值。
- 默認的clone()方法是淺拷貝:sheep = (Sheep)super.clone();
(2) 深拷貝
- 複製對象的所有基本數據類型的成員變量值。
- 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象。
- 實現方式:重寫clone()方法、通過對象序列化。
4.深拷貝方式1-重寫clone()
/** 鼠標類 **/
public class Mouse implements Cloneable{
// 鼠標名稱(羅技、蘋果)
private String name;
// 接口類型(usb、type-c)
private String usbType;
public Mouse(String name, String usbType){
this.name = name;
this.usbType = usbType;
}
// 因爲鼠標類中的屬性都是基本數據類型,所以使用默認的clone()方法即可。
@Override
protected Object clone(){
return super clone();
}
}
/** 計算機類 **/
public class Computer implements Cloneable{
// 電腦名稱(Macbook、聯想)
public String name;
// 鼠標
public Mouse mouse;
public Computer(){
}
// 深拷貝實現方式1:重寫clone()
@Override
protected Object clone(){
Object deep = null;
// 先使用默認的clone(), 完成對Computre類中的基本數據類型的拷貝
deep = super.clone();
// 對Computer類中的引用數據類型進行處理
Computer computer = (Computer)deep;
computer.mouse = (Mouse)mouse.clone();
return computer;
}
}
5.深拷貝方式2-序列化(推薦使用)
/** 鼠標類 **/
public class Mouse implements Serializable{
// 鼠標名稱(羅技、蘋果)
private String name;
// 接口類型(usb、type-c)
private String usbType;
}
/** 計算機類 **/
public class Computer implements Serializable{
// 電腦名稱(Macbook、聯想)
public String name;
// 鼠標
public Mouse mouse;
public Computer(){
}
// 深拷貝實現方式2:序列化
public Object deepClone(){
// 創建流對象
ByteArrayOutputStream byteOut = null;
ObjectOutputStream objectOut = null;
ByteArrayInputStream byteIn = null;
ObjectInputStream objectIn = null;
// 序列化
byteOut = new ByteArrayOutputStream();
objectOut = new ObjectOutputStream(byteOut);
objectOut.write(this); // 把當前這個對象以對象流的方式輸出
// 反列化
byteIn = new ByteArrayInputStream(objectOut.toByteArray());
objectIn = new ObjectInputStream(byteIn);
Computer com = (Computer)objectIn.readObjct();
// 流關閉,這裏應放在finally中
byteOut.close();
objectOut.close();
byteIn.close();
objectIn.close();
return com;
}
}
6.原型模式的注意事項
- 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能提高效率。
- 不用重新初始化對象,而是動態的獲得對象運行時的狀態。
- 如果原始對象發生變化,如增加了2個屬性,則就不需要修改其他創建對象時的代碼,其他克隆對象就會發生相應的變化。
- 在實現深克隆的時候,可能需要比較複雜的代碼。
- 對已有的類要它支持克隆時,要修改該類的代碼,違背了ocp原則。