大话设计模式笔记(七)の原型模式
舉個栗子
問題描述
要求有一個簡歷類,必須要有姓名,可以設置性別和年齡,可以設置工作經歷,最終需要三份簡歷。
簡單實現
簡歷類
/*** 簡歷類* Created by callmeDevil on 2019/7/13.*/
public class Resume {private String name;private String sex;private String age;private String timeArea;private String company;public Resume (String name) {this.name = name;}/*** 設置個人信息* @param sex* @param age*/public void setPersonalInfo(String sex, String age){this.sex = sex;this.age = age;}/*** 設置工作經歷* @param timeArea* @param company*/public void setWorkExperience(String timeArea, String company) {this.timeArea = timeArea;this.company = company;}/*** 顯示*/public void display () {System.out.println(String.format("%s %s %s", name, sex, age));System.out.println(String.format("工作經歷:%s %s", timeArea, company));}// 此處省略get、set方法}
測試
/*** 測試* Created by callmeDevil on 2019/7/13.*/
public class Test {public static void main(String[] args) {Resume resumeA = new Resume("callmeDevil");resumeA.setPersonalInfo("男", "24");resumeA.setWorkExperience("2018-2019", "偉大的航道");Resume resumeB = new Resume("callmeDevil");resumeB.setPersonalInfo("男", "24");resumeB.setWorkExperience("2018-2019", "偉大的航道");Resume resumeC = new Resume("callmeDevil");resumeC.setPersonalInfo("男", "24");resumeC.setWorkExperience("2018-2019", "偉大的航道");resumeA.display();resumeB.display();resumeC.display();}} 測試結果
callmeDevil 男 24
工作經歷:2018-2019 偉大的航道
callmeDevil 男 24
工作經歷:2018-2019 偉大的航道
callmeDevil 男 24
工作經歷:2018-2019 偉大的航道 存在的問題
跟手寫簡歷沒有差別,三份簡歷需要三份實例化,如果客戶需要二十份簡歷,那就得實例化二十次。
原型模式
定義
用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。
UML圖
代碼實現
/*** 簡歷類(實現JDK克隆接口)* Created by callmeDevil on 2019/7/13.*/
public class Resume implements Cloneable{private String name;private String sex;private String age;private String timeArea;private String company;public Resume (String name) {this.name = name;}/*** 設置個人信息* @param sex* @param age*/public void setPersonalInfo(String sex, String age){this.sex = sex;this.age = age;}/*** 設置工作經歷* @param timeArea* @param company*/public void setWorkExperience(String timeArea, String company) {this.timeArea = timeArea;this.company = company;}/*** 顯示*/public void display () {System.out.println(String.format("%s %s %s", name, sex, age));System.out.println(String.format("工作經歷:%s %s", timeArea, company));}/*** 實現克隆方法,可進行自己的克隆邏輯* @return* @throws CloneNotSupportedException*/@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}// 此處省略get、set方法} 測試
/*** 原型模式測試* Created by callmeDevil on 2019/7/13.*/
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Resume resumeA = new Resume("callmeDevil");resumeA.setPersonalInfo("男", "24");resumeA.setWorkExperience("2018-2019", "偉大的航道");// 只需要調用clone方法就可以實現新簡歷的生成,并且可以修改新簡歷的細節Resume resumeB = (Resume) resumeA.clone();resumeB.setWorkExperience("2019-2020", "新世界");Resume resumeC = (Resume) resumeA.clone();resumeC.setPersonalInfo("男", "25");resumeA.display();resumeB.display();resumeC.display();}} 測試結果
callmeDevil 男 24
工作經歷:2018-2019 偉大的航道
callmeDevil 男 24
工作經歷:2019-2020 新世界
callmeDevil 男 25
工作經歷:2018-2019 偉大的航道 好處
- 一般在初始化的信息不發生變化的情況下,克隆是最好的方法。這既隱藏了對象創建的細節,又對性能是大大的提高。
- 不用重新初始化對象,而是動態的獲得對象運行時的狀態。
淺復制與深復制
淺復制
在上面這個簡歷類中,如果字段是值類型(基本數據類型)的,則對該字段直接進行復制;如果是引用類型(String等),則/復制引用/但不/復制引用的對象/;因此,原始對象及其副本引用同一對象。
在此之前,我們先做一個簡單的測試
System.out.println("123" == "123");System.out.println("123".equals("123"));System.out.println(new String("123") == new String("123"));System.out.println(new String("123").equals(new String("123"))); 相信有點基礎的都知道答案吧?就不賣弄了,直接上結果
true
true
false
true 至于結果為什么會這樣,網上也許多分析,此處重點在淺復制的講解,因此不做過多敘述。
帶著上面的理解再看下面的內容。在可克隆的簡歷類例子中,基本數據類型就沒什么好測試的,有興趣的也可以將年齡改成 int 類型;對于其他 String 類型,就拿 company 字段來說,我們新建一個測試類看下是什么結果
public class Test2 {public static void main(String[] args) throws CloneNotSupportedException {Resume resumeA = new Resume("callmeDevil");resumeA.setWorkExperience("0", "偉大的航道");// 直接聲明StringResume resumeB = (Resume) resumeA.clone();// A 與 B 的公司兩種比較System.out.println(resumeA.getCompany() == resumeB.getCompany());System.out.println(resumeA.getCompany().equals(resumeB.getCompany()));resumeA.setWorkExperience("0", new String("偉大的航道"));//new 的方式創建StringResume resumeC = (Resume) resumeA.clone();// A 與 C 的公司兩種比較System.out.println(resumeA.getCompany() == resumeC.getCompany());System.out.println(resumeA.getCompany().equals(resumeC.getCompany()));}} 比對第一個“123”的例子,稍微思考下在比對下面結果看是否一致
true
true
true
true 是的,這就是淺復制。不論A簡歷的 company 是直接聲明的還是 new 出來的,在clone方法中都只會對 company 字段復制一份引用而已。因此才會出現 “==” 和 “equal()”的結果都是“true”。對于引用類型來說,這個字段所在類實現了clone方法是不夠的,它只會對引用類型復制一份引用,那如果連引用類型的字段也要創建一份新的數據,那便是 “深復制”。
- 淺復制就是,被復制的對象的所有變量都含有與原來對象相同的值,而所有其他對象的引用都仍然只想原來的對象。
- 深復制把引用對象的變量指向復制過的新對象,而不是援用的被引用的對象。
深復制
此處不糾結于如何對上述 String 的深復制。現將簡歷類進行改造,把“工作經歷”抽出成另一個類 WorkExperience
簡歷類2
/*** 簡歷類2(深復制)* Created by callmeDevil on 2019/7/13.*/
public class Resume2 implements Cloneable {private String name;private String sex;private String age;// 工作經歷private WorkExperience work;public Resume2 (String name) {this.name = name;this.work = new WorkExperience();}private Resume2(WorkExperience work) throws CloneNotSupportedException {this.work = (WorkExperience) work.clone();}/*** 設置個人信息* @param sex* @param age*/public void setPersonalInfo(String sex, String age){this.sex = sex;this.age = age;}/*** 設置工作經歷* @param timeArea* @param company*/public void setWorkExperience(String timeArea, String company) {// 此處賦值給 work 對象this.work.setWorkDate(timeArea);this.work.setCompany(company);}/*** 顯示*/public void display () {System.out.println(String.format("%s %s %s", name, sex, age));// 此處顯示 work 對象的值System.out.println(String.format("工作經歷:%s %s", work.getWorkDate(), work.getCompany()));}/*** 實現克隆方法,可進行自己的克隆邏輯* @return* @throws CloneNotSupportedException*/@Overrideprotected Object clone() throws CloneNotSupportedException {// 調用私有的構造方法,讓“工作經歷”對象克隆完成,然后再給這個“簡歷”對象// 相關的字段賦值,最終返回一個深復制的簡歷對象Resume2 obj = new Resume2(this.work);obj.setName(this.name);obj.setSex(this.sex);obj.setAge(this.age);return obj;}// 此處省略get、set方法} 工作經歷
/*** 工作經歷* Created by callmeDevil on 2019/7/13.*/
public class WorkExperience implements Cloneable{private String workDate;private String company;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}// 此處省略get、set方法} 測試類
/*** 深復制測試* Created by callmeDevil on 2019/7/13.*/
public class TestDeepClone {public static void main(String[] args) throws CloneNotSupportedException {Resume2 resumeA = new Resume2("callmeDevil");resumeA.setPersonalInfo("男", "24");resumeA.setWorkExperience("2018-2019", "偉大的航道");Resume2 resumeB = (Resume2) resumeA.clone();resumeB.setWorkExperience("2019-2020", "新世界");Resume2 resumeC = (Resume2) resumeA.clone();resumeC.setWorkExperience("2020-XXXX", "木葉忍者村");resumeA.display();resumeB.display();resumeC.display();}} 測試結果
callmeDevil 男 24
工作經歷:2018-2019 偉大的航道
callmeDevil 男 24
工作經歷:2019-2020 新世界
callmeDevil 男 24
工作經歷:2020-XXXX 木葉忍者村 可以看到,每次克隆都能將簡歷類中的工作經歷類一同新建,而不是單純的同個對象進行改變內容。如果是淺復制的實現,那么在相同測試類中會出現什么結果呢?應該能猜到吧,那就是三次輸出都是一樣的。
對于工作經歷類的淺復制實現本文就不描述了,有興趣的可以自行測試,很簡單,需要修改的地方有這么兩處:
- 簡歷類實現的克隆方法中直接調用 super.clone() 而不需要重寫。
- 取消工作經歷類實現克隆接口及其方法。
只需要更改這兩處,在相同的測試類中就能看到以下結果
callmeDevil 男 24
工作經歷:2020-XXXX 木葉忍者村
callmeDevil 男 24
工作經歷:2020-XXXX 木葉忍者村
callmeDevil 男 24
工作經歷:2020-XXXX 木葉忍者村 看到此處就不需要解釋了吧,每次克隆只是新實例化了“簡歷”,但三個“簡歷”中的“工作經歷”都是同一個,每次 set值 的時候都必將影響到所有對象,所以輸出的工作經歷都是相同的。這也同時說明了實現深復制的必要條件:
- 需要實現深復制的引用類型字段的類(比如上文中的工作經歷)必須實現 Cloneable 接口
- 該字段的所屬類(比如上文中的簡歷)實現的克隆方法中必須對該字段進行克隆與賦值
做到以上兩點,即可將原本淺復制的功能轉變為深復制。
總結
原型模式無非就是對指定創建的原型實例進行復制,再對新對象另做操作,省去重新 new 的過程。其中復制便分為淺復制與深復制。
轉載于:https://www.cnblogs.com/call-me-devil/p/11180403.html
總結
以上是生活随笔為你收集整理的大话设计模式笔记(七)の原型模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NOIP2016天天爱跑步
- 下一篇: leetcode-93-复原ip地址