創建型模式之原型模式
創建型模式之原型模式
定義
- 原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
- 這種模式是實現了一個原型介面,該介面用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要 在一個高代價的資料庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫調用。
- 原型模式可以分為淺克隆和深度克隆
角色
java語言中實現克隆的兩種方式
- 直接創建一個對象,然後設置成員變數的值
Obj obj=new Obj(); //創建一個新的對象
obj.setName(this.name); //設置其中變數的值
obj.setAge(this.age);- 實現cloneable介面
淺克隆
- 如果克隆的對象的成員變數是值類型的,比如int,double那麼使用淺克隆就可以實現克隆完整的原型對象,但是如果其中的成員變數有引用類型的,那麼這個引用類型的克隆過去的其實是地址,克隆對象的這個引用類型變數改變了,那麼原來變數的值也是會改變的。
- 簡單的說,淺克隆只能複製值類型的,對於引用類型的數據只能複製地址
實例
- 一個公司出版周報,那麼這個周報的格式一般是相同的,只是將其中的內容稍作修改即可。但是一開始沒有這個原型,員工每周都需要重新手寫這個周報,現在有了這個周報的原型,只需要在這個clone這個原型,然後在其基礎上修改即可。
- 其中的Cloneable就是抽象原型類
- 附件類(這個是一個引用類型的對象,驗證淺克隆只是複製其中的地址,如果兩個對象中的任何一個改變了這個變數的值,那麼另外一個也會隨之改變)
/* * 附件類,這個是周報的附件 */public class Attachment { private String name; // 名稱 public Attachment(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
- 周報的類(其中實現了Cloneable介面)
- 其中的clone()方法返回的就是一個克隆的對象,因此我們調用這個方法克隆一個新的對象
/* * 這個是周報類,這個類是實現介面Prototype這個介面的 */public class WeeklyLog implements Cloneable { private String name; // 姓名 private String date; // 日期 private String content; // 內容 private Attachment attachment; //附件,是一個引用對象,這個只能實現淺克隆 public WeeklyLog() { super(); } /** * 構造方法 */ public WeeklyLog(String name, String date, String content) { super(); this.name = name; this.date = date; this.content = content; } /** * 提供一個clone方法,返回的是一個clone對象 */ public WeeklyLog clone() { Object object = null; // 創建一個Object對象 try { object = super.clone(); // 直接調用clone方法,複製對象 return (WeeklyLog) object; // 返回即可 } catch (CloneNotSupportedException e) { System.out.println("這個對象不能複製....."); return null; } }}
- 測試類
- 測試淺克隆的值類型是是否完成複製了
- 測試引用類型的值能否完成克隆,還是只是複製了一個引用地址
- 從結果來看,對象是完成複製了,因為判斷兩個對象的地址是不一樣的,但是其中的引用類型的成員變數沒有完成複製,只是複製了一個地址
public class Client { public static void main(String[] args) throws CloneNotSupportedException { WeeklyLog p1 = new WeeklyLog("陳加兵", "第一周", "獲得勞動模範的稱號..."); // 創建一個對象 Attachment attachment = new Attachment("消息"); p1.setAttachment(attachment); // 添加附件 WeeklyLog p2 = p1.clone(); System.out.println(p1 == p2); // 判斷是否正確 p2.setName("Jack"); // 修改P2對象的內容 p2.setDate("第二周"); p2.setContent("工作認真....."); System.out.println(p2.getName()); // 返回true,可以知道這兩個附件的地址是一樣的 System.out.println(p1.getAttachment() == p2.getAttachment()); }}
總結
- 淺克隆對於值類型的數據可以複製成功,但是對於引用卡類型的數據只能複製一個地址,如果一個對象中的引用類型的變數的值改變了,那麼另外一個也會隨之改變
深度克隆
- 淺克隆只能完成複製值類型,深度克隆可以完成複製引用類型和值類型
條件
- 引用類型的變數類實現序列化(實現Serializabl介面)
- 需要克隆的類實現序列化(實現Serializable介面)
為什麼實現序列化
- 因為深度克隆的實現的原理是使用輸入和輸出流,如果想要將一個對象使用輸入和輸出流克隆,必須序列化。
實現
- 附件類(引用類型的成員變數,實現序列化)
/* * 附件類,這個是周報的附件 */public class Attachment implements Serializable{ private static final long serialVersionUID = -799959163401886355L; private String name; // 名稱 public Attachment(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
- 周報類(需要克隆的類,因為其中有引用類型的成員變數,因此需要實現序列化)
/* * 這個是周報類,這個類是實現介面Prototype這個介面的 */public class WeeklyLog implements Serializable { private static final long serialVersionUID = -8782492113927035907L; private String name; // 姓名 private String date; // 日期 private String content; // 內容 private Attachment attachment; // 附件,是一個引用對象,這個只能實現淺克隆 public WeeklyLog() { super(); } /** * 構造方法 */ public WeeklyLog(String name, String date, String content) { super(); this.name = name; this.date = date; this.content = content; } /** * 提供一個clone方法,返回的是一個clone對象 */ public WeeklyLog clone() { // 將對象寫入到對象流中 ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); try { ObjectOutputStream objectOutputStream = new ObjectOutputStream( arrayOutputStream); // 創建對象輸出流 objectOutputStream.writeObject(this); // 將這個類的對象寫入到輸出流中 } catch (IOException e) { e.printStackTrace(); return null; } // 將對象從流中讀出 ByteArrayInputStream arrayInputStream = new ByteArrayInputStream( arrayOutputStream.toByteArray()); WeeklyLog weeklyLog; try { ObjectInputStream objectInputStream = new ObjectInputStream( arrayInputStream);// 新建對象輸入流 weeklyLog = (WeeklyLog) objectInputStream.readObject(); // 讀取對象從流中 return weeklyLog; } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } }}
- 測試類
- 從中可以看出其中的附件地址是不同的,如果一個對象的附件變數改變了,那麼另外一個將保持不變,因此實現了深度克隆,是兩個完全不同的對象
public class Client { public static void main(String[] args) throws CloneNotSupportedException { WeeklyLog p1 = new WeeklyLog("陳加兵", "第一周", "獲得勞動模範的稱號..."); // 創建一個對象 Attachment attachment = new Attachment("消息"); p1.setAttachment(attachment); // 添加附件 WeeklyLog p2 = p1.clone(); System.out.println(p1 == p2); // 判斷是否正確 p2.setName("Jack"); // 修改P2對象的內容 p2.setDate("第二周"); p2.setContent("工作認真....."); System.out.println(p2.getName()); //返回false,可以看出這個是不同的地址,因此完成了深克隆 System.out.println(p1.getAttachment() == p2.getAttachment()); }}
總結
- 因為深度克隆使用的是將對象寫入輸入和輸出流中的,因此需要實現序列化,否則將不能完成
總結
- 淺克隆只能克隆對象中的值類型,不能克隆有引用類型成員變數的對象
- 使用深度克隆:
- 引用類型的成員變數的類必須實現序列化
- 需要克隆的類必須實現序列化
推薦閱讀:
※面向對象&設計模式
※遊戲開發與程序設計知識總結01——設計模式
※結構型模式之適配器模式
※《Node.js設計模式(第2版)》試讀 & 送書活動
※Go編程技巧之「延後抽象」
TAG:設計模式 |