原型模式(Prototype Pattern)
原型模式主要用於創建重複的對象,同時又能保證性能,所以是創建型
在使用原型模式時,我們需要首先創建一個原型對象
,再通過複製
這個原型對象,來創建更多
的同類型
的對象。
使用原型複製對象,性能很高
,所以常用於創建大對象
,或者初始化繁瑣
的對象:比如遊戲裏面的地圖等等。
適用場景:
一是類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等;
二是通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式;
三是一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。
注意:原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone的方法創建一個對象,然後由工廠方法提供給調用者。
UML的相關知識,可以訪問我的另外一篇博文
網上的接口繼承的方式實現起來較爲簡單:
通過ClassA
implements
Cloneable
,然後加入clone()方法即可【但是這是 淺拷貝
】
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
測試代碼:
// ClassA 實現了 Cloneable 接口
ClassA a = new ClassA();
a.setXXX();
ClassA b = (ClassA) c.clone();
b.setXXX();
以上方法不過多贅述
這篇文章主要通過調用對象,實現淺拷貝和深拷貝。
看完圖說理論:
淺Copy是指被複制的對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用都指向原來的對象。簡單點說就是,只複製了引用,而沒有複製真正的對象內容。
深Copy是指被複制的對象的所有變量都含有與原來對象相同的值,屬性中的對象都指向被複制過的新對象中屬性,而不再是原型對象中的屬性。簡單點說,就是深Copy把所有的對象的引用以及對象都複製了一遍,在堆中是存在兩個相互獨立的對象,以及屬性中的對象也是相互獨立的。
說完了淺拷貝
和深拷貝
,正式開始編碼,還是拿遊戲地圖舉例子
非常重要
:大家着重看clone()方法
首先實現淺拷貝
(Shallow Copy):
總地圖GameMap
代碼:
public class ShallowCopyGameMap {
private String tree;
private String mountain;
public String getTree() {
return tree;
}
public void setTree(String tree) {
this.tree = tree;
}
public String getMountain() {
return mountain;
}
public void setMountain(String mountain) {
this.mountain = mountain;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
@Override
public String toString() {
return "{" +
"tree='" + tree + '\'' +
", mountain='" + mountain + '\'' +
'}';
}
}
LOL遊戲地圖LOLGameMap
如下:
/**
* @description:
* 淺拷貝
*/
public class ShallowCopyLOLGameMap implements Cloneable {
private String dragon;
private ShallowCopyGameMap gameMap; // 關聯GameMap對象
public ShallowCopyLOLGameMap(String dragon,
ShallowCopyGameMap gameMap) {
this.dragon = dragon;
this.gameMap = gameMap;
}
public String getDragon() {
return dragon;
}
public void setDragon(String dragon) {
this.dragon = dragon;
}
public ShallowCopyGameMap getGameMap() {
return gameMap;
}
public void setGameMap(String tree, String mountain) {
gameMap.setTree(tree);
gameMap.setMountain(mountain);
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
@Override
public String toString() {
return "{" +
"dragon='" + dragon + '\'' +
", gameMap=" + gameMap +
'}';
}
}
這兩個類基本相似,都實現了Cloneable
接口
接下來進行測試:
public static void main(String[] args) {
System.out.println("淺拷貝測試");
ShallowCopyGameMap gameMap = new ShallowCopyGameMap();
ShallowCopyLOLGameMap lolGameMap1 =
new ShallowCopyLOLGameMap("納什男爵", gameMap);
lolGameMap1.setGameMap("樹","山");
System.out.println("地圖1:" + lolGameMap1);
ShallowCopyLOLGameMap lolGameMap2 =
(ShallowCopyLOLGameMap) lolGameMap1.clone();
lolGameMap2.setGameMap("樹2", "山2");
System.out.println("地圖2:" + lolGameMap2);
ShallowCopyLOLGameMap lolGameMap3 =
(ShallowCopyLOLGameMap) lolGameMap2.clone();
lolGameMap3.setGameMap("樹3", "山3");
System.out.println("地圖3:" + lolGameMap3);
System.out.println("重新查看前兩個地圖: ");
System.out.println("地圖1:" + lolGameMap1);
System.out.println("地圖2:" + lolGameMap2);
}
測試結果:
淺拷貝測試
地圖1:{dragon='納什男爵', gameMap={tree='樹', mountain='山'}}
地圖2:{dragon='納什男爵', gameMap={tree='樹2', mountain='山2'}}
地圖3:{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖:
地圖1:{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}
地圖2:{dragon='納什男爵', gameMap={tree='樹3', mountain='山3'}}
這就是淺拷貝
的測試結果,實際開發中要非常留意這個問題,最好在測試時將之前的對象再進行測試輸出一次
。
我這裏提供兩種方式實現深拷貝:串行化
和非串行化
非串行化方式
實現:(還是通過實現Cloneable接口,注意這裏的clone()方法與淺拷貝的區別)
深拷貝(非串行化):總地圖GameMap
代碼:代碼與淺拷貝相同,不過多解釋
public class DeepCopyGameMap2 implements Cloneable {
private String tree;
private String mountain;
public String getTree() {
return tree;
}
public void setTree(String tree) {
this.tree = tree;
}
public String getMountain() {
return mountain;
}
public void setMountain(String mountain) {
this.mountain = mountain;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
@Override
public String toString() {
return "{" +
"tree='" + tree + '\'' +
", mountain='" + mountain + '\'' +
'}';
}
}
深拷貝(非串行化):LOL遊戲地圖LOLGameMap
如下:
特別注意
:注意這裏的clone()方法與淺拷貝的區別:
/**
* @description:
* 通過轉換爲二進制進行深拷貝
*/
public class DeepCopyLOLGameMap2 implements Cloneable {
private String dragon;
private DeepCopyGameMap2 deepCopyGameMap;
public DeepCopyLOLGameMap2(String dragon,
DeepCopyGameMap2 deepCopyGameMap) {
this.dragon = dragon;
this.deepCopyGameMap = deepCopyGameMap;
}
public String getDragon() {
return dragon;
}
public void setDragon(String dragon) {
this.dragon = dragon;
}
public DeepCopyGameMap2 getGameMap() {
return deepCopyGameMap;
}
public void setDeepCopyGameMap2(String tree, String mountain) {
deepCopyGameMap.setTree(tree);
deepCopyGameMap.setMountain(mountain);
}
// 非串行化方式, 在LOLGameMap的clone()方法中調用總地圖的clone()方法
public DeepCopyLOLGameMap2 clone() {
DeepCopyLOLGameMap2 clone = null;
try {
clone = (DeepCopyLOLGameMap2)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
assert clone != null; // 非空判斷,可以忽略
// 深淺拷貝的區別在這裏!!!!!
// 使用了GameMap總地圖的clone()方法
// GameMap的字段也是淺拷貝中出錯變化的地方
clone.deepCopyGameMap =
(DeepCopyGameMap2) deepCopyGameMap.clone();
return clone;
}
@Override
public String toString() {
return "{" +
"dragon='" + dragon + '\'' +
", deepCopyGameMap=" + deepCopyGameMap +
'}';
}
}
深拷貝(非串行化)測試代碼:
public static void main(String[] args) {
System.out.println("深拷貝測試---- 非串行化方式");
DeepCopyGameMap2 deepCopyGameMap = new DeepCopyGameMap2();
deepCopyGameMap.setTree("樹");
deepCopyGameMap.setMountain("山");
DeepCopyLOLGameMap2 lolGameMap1 =
new DeepCopyLOLGameMap2("納什男爵",deepCopyGameMap);
lolGameMap1.setDeepCopyGameMap2("樹","山");
System.out.println("地圖1:" + lolGameMap1);
DeepCopyLOLGameMap2 lolGameMap2 =
(DeepCopyLOLGameMap2) lolGameMap1.clone();
lolGameMap2.setDeepCopyGameMap2("樹2", "山2");
System.out.println("地圖2:" + lolGameMap2);
DeepCopyLOLGameMap2 lolGameMap3 =
(DeepCopyLOLGameMap2) lolGameMap2.clone();
lolGameMap3.setDeepCopyGameMap2("樹3", "山3");
System.out.println("地圖3:" + lolGameMap3);
System.out.println("重新查看前兩個地圖: ");
System.out.println("地圖1:" + lolGameMap1);
System.out.println("地圖2:" + lolGameMap2);
}
}
測試結果如下:
深拷貝測試---- 非串行化方式
地圖1:{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2:{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
地圖3:{dragon='納什男爵', deepCopyGameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖:
地圖1:{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2:{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
串行化方式
實現:(必須實現Serializable)
深拷貝(串行化):總地圖GameMap
代碼:
public class DeepCopyGameMap implements Serializable {
private String tree;
private String mountain;
public String getTree() {
return tree;
}
public void setTree(String tree) {
this.tree = tree;
}
public String getMountain() {
return mountain;
}
public void setMountain(String mountain) {
this.mountain = mountain;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
@Override
public String toString() {
return "{" +
"tree='" + tree + '\'' +
", mountain='" + mountain + '\'' +
'}';
}
}
深拷貝(串行化):LOL遊戲地圖LOLGameMap
如下:
/**
* @description:
* 通過串行化方式進行深拷貝
*/
public class DeepCopyLOLGameMap implements Serializable {
private String dragon;
private DeepCopyGameMap deepCopyGameMap;
public DeepCopyLOLGameMap(String dragon,
DeepCopyGameMap deepCopyGameMap) {
this.dragon = dragon;
this.deepCopyGameMap = deepCopyGameMap;
}
public String getDragon() {
return dragon;
}
public void setDragon(String dragon) {
this.dragon = dragon;
}
public DeepCopyGameMap getDeepCopyGameMap() {
return deepCopyGameMap;
}
public void setDeepCopyGameMap(String tree, String mountain) {
deepCopyGameMap.setTree(tree);
deepCopyGameMap.setMountain(mountain);
}
// 串行化方式進行深拷貝
public DeepCopyLOLGameMap clone() {
DeepCopyLOLGameMap clone = null;
try {
//寫入當前對象的二進制流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//讀出二進制流產生新的對象
ByteArrayInputStream bais =
new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
clone = (DeepCopyLOLGameMap)ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clone;
}
@Override
public String toString() {
return "{" +
"dragon='" + dragon + '\'' +
", deepCopyGameMap=" + deepCopyGameMap +
'}';
}
}
深拷貝(串行化)測試代碼:
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("深拷貝測試--- 串行化方式");
DeepCopyGameMap deepCopyGameMap = new DeepCopyGameMap();
DeepCopyLOLGameMap lolGameMap1 =
new DeepCopyLOLGameMap("納什男爵", deepCopyGameMap);
lolGameMap1.setDeepCopyGameMap("樹","山");
System.out.println("地圖1:" + lolGameMap1);
DeepCopyLOLGameMap lolGameMap2 =
(DeepCopyLOLGameMap) lolGameMap1.clone();
lolGameMap2.setDeepCopyGameMap("樹2", "山2");
System.out.println("地圖2:" + lolGameMap2);
DeepCopyLOLGameMap lolGameMap3 =
(DeepCopyLOLGameMap) lolGameMap2.clone();
lolGameMap3.setDeepCopyGameMap("樹3", "山3");
System.out.println("地圖3:" + lolGameMap3);
System.out.println("重新查看前兩個地圖: ");
System.out.println("地圖1:" + lolGameMap1);
System.out.println("地圖2:" + lolGameMap2);
}
測試結果如下:
深拷貝測試--- 串行化方式
地圖1:{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2:{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
地圖3:{dragon='納什男爵', deepCopyGameMap={tree='樹3', mountain='山3'}}
重新查看前兩個地圖:
地圖1:{dragon='納什男爵', deepCopyGameMap={tree='樹', mountain='山'}}
地圖2:{dragon='納什男爵', deepCopyGameMap={tree='樹2', mountain='山2'}}
注意LOLGameMap
的clone()
方法
總結一下:
原型模式通過Object的clone()方法實現,由於是內存操作,無視構造方法和訪問權限,直接獲取新的對象。但對於引用類型,需使用深拷貝,其它淺拷貝即可。
代碼已經上傳到Git:請點擊訪問
如果大家對於原型模式還有更多的使用技巧和使用心得,歡迎評論並在評論中分享自己的鏈接!