大話設計模式讀書筆記(原型模式)
人物:小菜,大鳥
事件:小菜正在準備求職見面會,列印了很多簡歷,大鳥讓小菜試著用程式寫簡歷
瞭解原型模型:
1.通過用原始的邏輯實現幾份相同簡歷的列印
2.思考有沒有一種模型可以幫助小菜不用新建物件,直接複製簡歷 --引出原型模型
3.通過試驗值複製和引用複製,知道了深複製和淺複製,然後引出深複製的實現原理和實現內容
小菜簡歷程式碼初步實現
簡歷類:
@Slf4j 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() { log.info("名字:{} 性別:{} 年齡:{}", name, sex, age); log.info("工作經歷:{}, {}", timeArea, company); } }
列印三份,客戶端呼叫程式碼:
public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鳥"); Resume b = new Resume("大鳥"); Resume c = new Resume("大鳥"); a.setPersonalInfo("男", "29"); b.setPersonalInfo("男", "29"); c.setPersonalInfo("男", "29"); a.setWorkExperience("2019-2020", "凡人修仙公司"); b.setWorkExperience("2019-2020", "凡人修仙公司"); c.setWorkExperience("2019-2020", "凡人修仙公司"); a.display(); b.display(); c.display(); } }
大鳥:這樣如果要列印20份,也就要例項20個物件,如果寫好20份後,如果要改下年份,那不是20個要一個一個改麼?你其實可以先這樣改下:
public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鳥"); a.setPersonalInfo("男", "29"); a.setWorkExperience("2018-2020", "凡人修仙公司"); Resume b = a; Resume c = a; a.display(); b.display(); c.display(); } }
大鳥:這樣就比剛才好多了,其實是傳引用,不是傳值,就像一份簡歷,每次複製與內容有關,與用什麼紙無關,下面我們就可以開始瞭解原型模型了
簡歷的原型模型實現
原型模型:用原型模型指定建立物件的種類,並通過拷貝這些原型建立新的物件(而且不用知道建立的細節)
簡歷類:
@Slf4j 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() { log.info("名字:{} 性別:{} 年齡:{}", name, sex, age); log.info("工作經歷:{}, {}", timeArea, company); } @Override public Object clone() { Resume resume = null; try { resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } }
客戶端呼叫:
@Slf4j public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鳥"); a.setPersonalInfo("男", "29"); a.setWorkExperience("2019-2020", "凡人修仙功法公司"); Resume b = (Resume) a.clone(); b.setWorkExperience("2020-2021", "凡人修仙寶器公司"); Resume c = (Resume) a.clone(); c.setWorkExperience("2020-2021", "凡人修仙煉體公司"); a.display(); b.display(); c.display(); } }
大鳥:對的,這樣實現後,這樣每次複製簡歷或者要修改簡歷,都是直接克隆,而不是再去建立一個新的例項,大大提高了效能,還隱藏了實現的細節
大鳥:但注意了,我們剛才複製的,都是String型別,也就是都是值型別,但如果是引用型別,就只會複製引用,不會複製引用的型別,因此原始物件及其複本引用同一物件
如下:
@Slf4j @Data public class Resume implements Cloneable { private String name; private String sex; private String age; private String timeArea; private String company; private WorkExperience work; public Resume(String name) { this.name = name; work = new WorkExperience(); } /** * 設定個人資訊 * * @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.setTimeArea(timeArea); work.setCompany(company); } /** * 顯示 */ public void display() { log.info("名字:{} 性別:{} 年齡:{}", name, sex, age); log.info("工作經歷:{}, {}", work.getTimeArea(), work.getCompany()); } @Override public Object clone() { Resume resume = null; try { resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } }
其中:private WorkExperience work是引用工作型別的物件
在簡歷例項化時,同時例項化工作經歷:
public Resume(String name) { this.name = name; work = new WorkExperience(); }
在設定工作經歷時,給物件的兩屬性值賦值:
/** * 設定工作經歷 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { work.setTimeArea(timeArea); work.setCompany(company); }
這時如果再執行之前的客戶端程式碼,則都只顯示a物件的資訊
大鳥小結:如果是值物件,直接複製沒有問題,如果是引用型別,只是複製了引用,對引用的物件還是指向了原來的物件,這叫做淺複製。淺複製指被複制物件的所有變數都含有與原來物件相同的值,而所有的對其他物件的引用都仍然指向原來的物件。當我們需要把所有引用的物件都複製一遍時,這就需要深複製,深複製會把引用物件的變數指向複製過的新物件,而不是原有的被引用的物件
深複製:
現在工作經歷類中,複製傳進來的值:
@Data public class WorkExperience implements Cloneable { private String timeArea; private String company; public Object cloneInfo() { Object object = null; try { object = (Object)this.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return object; } }
然後在簡歷類中,先將傳進來的工作經歷複製,將a裡工作經歷的內容重新賦值後傳了一個新物件給b,b也就得到了改變的值:
@Slf4j @Data public class Resume implements Cloneable { private String name; private String sex; private String age; private String timeArea; private String company; private WorkExperience work; public Resume(String name) { this.name = name; work = new WorkExperience(); } /** * 設定個人資訊 * * @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.setTimeArea(timeArea); work.setCompany(company); } /** * 顯示 */ public void display() { log.info("名字:{} 性別:{} 年齡:{}", name, sex, age); log.info("工作經歷:{}, {}", work.getTimeArea(), work.getCompany()); } @Override public Object clone() { Resume obj = new Resume(this.work); obj.name = this.name; obj.sex = this.sex; obj.age = this.age; return obj; } private Resume(WorkExperience work) { try { this.work = (WorkExperience)work.cloneInfo(); } catch (Exception e) { e.printStackTrace(); } } }
這樣得到的結果就不像淺複製一樣都是一樣的了,而是:
名字:大鳥 性別:男 年齡:29 工作經歷:2019-2020, 凡人修仙功法公司 名字:大鳥 性別:男 年齡:29 工作經歷:2020-2021, 凡人修仙寶器公司 名字:大鳥 性別:男 年齡:29 工作經歷:2020-2021, 凡人修仙煉體公司 名字:大鳥 性別:男 年齡:29