原型模式
序言:今天我們來聊一下原型模式,我個人認為原型模式的命名不太好理解,稱呼其為克隆模式會更妥當一點。原型模式的目的是通過複製一個現有的對象來生成一個新的對象,而不是通過實例化的方法。
原型模式的基本介紹
-
用已經創建的實例對象作為原型,通過複製對象來創建一個和原型相同的對象或相似的新對象
-
原型模式屬於創建型模式,主要通過
Cloneable
介面去完成 對象的複製
在原型模式結構圖中,會有這麼幾個角色
- 抽象原型角色(Prototype):是聲明克隆方法的介面,是所有原型類的公共父類
- 具體原型角色(Realizetype):它實現在抽象原型類中所聲明的克隆方法,在克隆方法中返回一個克隆對象
- 訪問角色(PrototypeTest): 使用具體原型類中的 clone() 方法來複制新的對象
需要注意的點:
在 Java 中 能夠克隆的 Java類 務必得 實現 Cloneable
介面,表示這個 類 能夠被 「複製」,至於這個 複製的效果 則與我們的實現有關,通常 clone()方法滿足以下的條件:
- 對任何的對象x,都有:x.clone()!=x 。換言之,克隆對象與元對象不是一個對象
- 對任何的對象x,都有:x.clone().getClass==x.getClass(),換言之,克隆對象與元對象的類型一樣
- 對任何的對象x,如果 equals() 方法編寫得當的話, 那麼x.clone().equals(x)應該是成立的
在正式開始原型模式之前,我們先了解兩個概念 淺克隆和深克隆,淺克隆和深克隆的主要區別在於是否支援引用類型的成員變數的複製
原型模式(淺克隆)
在淺克隆中,如果原型對象的成員變數是值類型,將複製一份給克隆對象;如果原型對象的成員變數是引用類型,則將引用對象的地址 複製 一份給克隆對象,也就是說原型對象和克隆對象的成員變數指向 相同 的記憶體地址
下面我們以 花園和花 為案例
假設我們有一個花園,我們在花園裡種植上好的牡丹花,有一天突然想照著牡丹園再建一個一模一樣的園子,只是在花園中改種玫瑰
/**
* 花園
*/
@Data
@AllArgsConstructor
public class Garden implements Cloneable {
//面積
private double area;
private Flower flower;
@Override
protected Garden clone() {
Garden garden = null;
try {
garden = (Garden) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower {
private String name;
private String color;
}
下面來測試一下原型模式(淺克隆)
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹園
Garden peonyGarden = new Garden(1000, peony);
System.out.println("初始的牡丹園:" + peonyGarden);
// 牡丹園的複製建造 但是花改成玫瑰 改名玫瑰園
Garden roseGarden = peonyGarden.clone();
//淺複製只會複製引用地址,並沒有重新複製一個對象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改為種植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰園:" + roseGarden);
System.out.println("牡丹園:" + peonyGarden);
}
}
我們發現,我們想在新開拓的花園中改種玫瑰後,會影響原花園的花卉的品種,這顯然不是我們想要的效果
從這裡我們也看出來了,淺克隆 在克隆一個對象的 引用類型的成員變數時 只是複製其地址值,並沒有複製該對象
原型模式(深克隆)
在深克隆中,無論原型對象的成員變數是值類型還是引用類型,都將複製一份給克隆對象,深克隆將原型對象的所有引用對象也複製一份給克隆對象。簡單來說,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變數也將複製。
在 Java 中想實現 深克隆,通常有 兩種方式
- 對於克隆對象的 引用類型,逐層克隆
- 使用序列化方式
逐層克隆
逐層克隆意味著,如果我們要拷貝一個對象,該對象中 若有多個引用類型的成員變數,它們都要實現克隆方法,若嵌套多層引用類型的成員變數,則逐層 實現 Cloneable
介面
**
* 花園
*/
@Data
@AllArgsConstructor
public class Garden implements Cloneable {
//面積
private double area;
private Flower flower;
@Override
protected Garden clone() {
Garden garden = null;
try {
garden = (Garden) super.clone();
garden.flower = garden.flower.clone();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower implements Cloneable {
private String name;
private String color;
@Override
protected Flower clone() {
Flower flower = null;
try {
flower = (Flower) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return flower;
}
}
下面我們測試一下,使用逐層實現 Cloneable
介面 而完成的深克隆
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹園
Garden peonyGarden = new Garden(1000, peony);
// 牡丹園的複製建造 但是花改成玫瑰 改名玫瑰園
Garden roseGarden = peonyGarden.clone();
// 深克隆 面對引用類型的成員變數 也重新複製了一個對象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改為種植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰園:" + roseGarden);
System.out.println("牡丹園:" + peonyGarden);
}
}
序列化
深克隆模式,採取序列化這種方式可能更簡單一些,所以的引用類型 成員變數,都實現序列化介面,原型對象 自實現 deepClone
方法即可
/**
* 花園
*/
@Data
@AllArgsConstructor
public class Garden implements Serializable {
private static final long serialVersionUID = 2231850409918603998L;
//面積
private double area;
private Flower flower;
//深拷貝-方式2 使用序列化方式
public Garden deepClone(){
//創建流對象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Garden garden = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //當前這個對象以對象的方式輸出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
garden = (Garden) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return garden;
}
}
@Data
@AllArgsConstructor
public class Flower implements Serializable {
private static final long serialVersionUID = 126839939664064143L;
private String name;
private String color;
@Override
protected Flower clone() {
Flower flower = null;
try {
flower = (Flower) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return flower;
}
}
下面我們進行序列化深克隆的測試
public class Client {
public static void main(String[] args) {
// 牡丹花
Flower peony = new Flower("牡丹", "blue");
// 牡丹園
Garden peonyGarden = new Garden(1000, peony);
// 牡丹園的複製建造 但是花改成玫瑰 改名玫瑰園
Garden roseGarden = peonyGarden.deepClone();
// 深克隆 面對引用類型的成員變數 也重新複製了一個對象
System.out.println(peonyGarden.getFlower() == roseGarden.getFlower());
// 改為種植玫瑰花
roseGarden.getFlower().setName("玫瑰");
roseGarden.getFlower().setColor("red");
System.out.println("玫瑰園:" + roseGarden);
System.out.println("牡丹園:" + peonyGarden);
}
}
從上述我們可以看出,深克隆不僅在堆記憶體上開闢了空間以存儲複製出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以複製,重新開闢了堆空間存儲。