程序猿學社的GitHub,歡迎Star
github技術專題
本文已記錄到github
前言
通過前面兩章,我們已經知道單例模式,工廠模式,本文就來看一看原型模式。
概念
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。原型模式屬於一種創建型的設計模式
- 可以理解爲克隆技術,克隆羊
- copy技術,直接原封不動的copy一個內容
- 修真小說,到了元嬰期,就是肉體被毀滅,只要元嬰沒有被滅,還可以復活。
- 王者榮耀裏面最新出來的一個英雄,鏡,可以製造一個分身。分身也擁有攻擊力
實戰
場景
隔壁老王: “社長,你們公司需要填寫週報嗎?”
社長: “需要填寫的,而且每週,都會有一次周例會”
隔壁老王: “填寫週報好浪費時間,還得排版,再填寫內容”
社長: “你是怎麼實現的”
隔壁老王: “我每次重新生成一個excel,再排版的”
社長: “老鐵,你這太實誠咯”
傳統方式
package com.cxyxs.designmode.prototype;
import lombok.Data;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:42
* Modified By:
*/
@Data
public class Weekly {
private String name;
/** 本週計劃 */
private String weekPlan;
/** 下週計劃 */
private String nextWeekPlan;
/** 填寫時間 */
private Date createTime;
public Weekly(String name, String weekPlan, String nextWeekPlan, Date createTime) {
this.name = name;
this.weekPlan = weekPlan;
this.nextWeekPlan = nextWeekPlan;
this.createTime = createTime;
}
}
- 使用@Data註解,實際上就是簡化代碼,無需寫set get方法,使用了lombok插件
package com.cxyxs.designmode.prototype;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:45
* Modified By:
*/
public class WeeklyTest {
public static void main(String[] args) {
Weekly weekly = new Weekly("隔壁小王","用戶模塊開發","訂單模塊開發",new Date());
System.out.println("--------本週週報--------");
System.out.println(weekly.toString());
System.out.println("--------下週週報--------");
Weekly weekly1 = new Weekly("隔壁小王","訂單模塊開發","訂單模塊開發相關單元測試",new Date());
System.out.println(weekly1.toString());
}
}
隔壁老王: “社長,有沒有什麼方法,可以簡化週報這塊,提高我編寫週報的效率”
社長: “直接copy一份,上一次編寫的週報(原型模式),在此之上,再做修改”
原型模式
package com.cxyxs.designmode.prototype.shallow;
import lombok.Data;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:42
* Modified By:
*/
@Data
public class Weekly implements Cloneable{
private String name;
/** 本週計劃 */
private String weekPlan;
/** 下週計劃 */
private String nextWeekPlan;
/** 填寫時間 */
private Date createTime;
public Weekly(String name, String weekPlan, String nextWeekPlan, Date createTime) {
this.name = name;
this.weekPlan = weekPlan;
this.nextWeekPlan = nextWeekPlan;
this.createTime = createTime;
}
@Override
protected Object clone() throws CloneNotSupportedException {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
通過上面的代碼,我們小小的梳理一下,實現的過程
實現原型模式就兩步:
- 實現Cloneable接口
- 重寫clone方法
- 學過java的社友,都知道,Object是一切類的老祖宗,代碼裏面的super.clone(),實際上,就是調用Object的clone方法,通過查看他的源碼,發現,又遇到native方法,說明實現克隆不是通過java實現的(c或者c++)
package com.cxyxs.designmode.prototype.shallow;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:45
* Modified By:
*/
public class WeeklyTest {
public static void main(String[] args) throws Exception{
Date date = new Date();
Weekly weekly = new Weekly("隔壁小王","用戶模塊開發","訂單模塊開發",date);
System.out.println("--------本週週報--------");
System.out.println(weekly.toString());
System.out.println("--------下週週報--------");
Weekly weekly1 = (Weekly) weekly.clone(); //使用克隆(copy方式)
//Weekly weekly1 = new Weekly("隔壁小王","訂單模塊開發","訂單模塊開發相關單元測試",new Date());
System.out.println(weekly1.toString());
System.out.println(weekly == weekly1);
}
}
- 可以發現結果爲false,說明重新生成了一個對象,而且克隆的對象和原來的一模一樣,回顧一下,我們第一章學習過的單例模式,又增加了一種,可以破解單例的方法。
單例模式,誰說程序猿沒有女(男)朋友?
社長: “實際上,上面這種寫法還存在一點的問題”
隔壁老王: “社長,存在什麼問題”
社長: “上面這種寫法,只能實現淺克隆,沒有實現完全的克隆,我們先通過一個例子來看一看把”
淺克隆
package com.cxyxs.designmode.prototype.shallow;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:45
* Modified By:
*/
public class WeeklyTest1 {
public static void main(String[] args) throws Exception{
Date date = new Date();
Weekly weekly = new Weekly("隔壁小王","用戶模塊開發","訂單模塊開發",date);
Weekly weekly1 = (Weekly) weekly.clone(); //使用克隆(copy方式)
System.out.println(weekly.toString());
System.out.println(weekly1.toString());
//修改克隆後對象的值
date.setTime(1546315871);
System.out.println("--------------");
System.out.println(weekly.toString());
System.out.println(weekly1.toString());
}
}
- 先實現克隆後,我們再修改createTime的值,可以發現,修改後,克隆前和克隆後的值都一樣,說明這兩個對象的指向的都是同一個地方。
淺克隆
- 淺克隆會進行值傳遞(只是針對基本數據類型)
- 如果是引用數據類型,淺克隆會進行引用傳遞,實際上,就是把引用地址copy一份給新的對象,實際上,都是指向同一個地址。
深克隆
重寫clone方法實現深克隆
package com.cxyxs.designmode.prototype.shallow;
import lombok.Data;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:42
* Modified By:
*/
@Data
public class Weekly implements Cloneable{
private String name;
/** 本週計劃 */
private String weekPlan;
/** 下週計劃 */
private String nextWeekPlan;
/** 填寫時間 */
private Date createTime;
public Weekly(String name, String weekPlan, String nextWeekPlan, Date createTime) {
this.name = name;
this.weekPlan = weekPlan;
this.nextWeekPlan = nextWeekPlan;
this.createTime = createTime;
}
@Override
protected Object clone() throws CloneNotSupportedException {
try {
Weekly weekly = (Weekly) super.clone();
//把數據克隆
Date clone = (Date) createTime.clone();
weekly.setCreateTime(clone);
return weekly;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
- 因爲考慮到createTime,指向的是同一個引用,所以,我們每次克隆的時候,都把這個字段重新克隆一次。
- 通過上圖,可以看出,修改時間,只是克隆前的值發生了改變。 可以這樣理解,在修真小說內,到了分神期,可以修煉第二分身,實際上,你攻擊本體時,只是本體受到傷害,就算本體死亡,第二分身還可以繼續存活。
隔壁老王: “社長,看了一下,你寫的代碼,這還是屬性的,如果有上百個屬性,我這一個個改,也忒麻煩咯。有沒有不用這樣一個個設置的方法”
社長: “這種方法的copy,實際上是內存的copy,直接操作內存的,也是性能最好的,如果你覺得這種方式太麻煩了,我們可以通過序列化和反序列的方式來實現”
通過序列化實現深克隆
package com.cxyxs.designmode.prototype.shallow.seri;
import lombok.Data;
import java.io.*;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:42
* Modified By:
*/
@Data
public class Weekly implements Serializable {
private String name;
/** 本週計劃 */
private String weekPlan;
/** 下週計劃 */
private String nextWeekPlan;
/** 填寫時間 */
private Date createTime;
public Weekly(String name, String weekPlan, String nextWeekPlan, Date createTime) {
this.name = name;
this.weekPlan = weekPlan;
this.nextWeekPlan = nextWeekPlan;
this.createTime = createTime;
}
public Object copy() throws Exception{
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Weekly weekly = (Weekly) ois.readObject();
return weekly;
}
}
實現序列化兩部曲:
- 實現Serializable接口
- 實現序列化和反序列化對應的代碼
package com.cxyxs.designmode.prototype.shallow.seri;
import java.util.Date;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/4/12 18:45
* Modified By:
*/
public class WeeklyTest2 {
public static void main(String[] args) throws Exception{
Date date = new Date();
Weekly weekly = new Weekly("隔壁老王","用戶模塊開發","訂單模塊開發",date);
Weekly weekly1 = (Weekly) weekly.copy(); //使用克隆(copy方式)
System.out.println(weekly.toString());
System.out.println(weekly1.toString());
//修改克隆後對象的值
date.setTime(11);
System.out.println("--------------");
System.out.println(weekly.toString());
System.out.println(weekly1.toString());
}
}
- 通過這種方式實現對象的深度克隆,無需一個個設置引用類型屬性的clone方法。
總結: 如果,需要創建大量的對象,建議使用原型模式。實際上他是不符合ocp原則的。應該實現對擴展開放,對修改關閉。不會調用類的重構方法 ,可以破解單例模式。
原創不易,不要白嫖,覺得有用的社友,給我點贊,讓更多的老鐵看到這篇文章。
因技術能力有限,如文中有不合理的地方,希望各位大佬指出,在下方評論留言,謝謝,希望大家一起進步,一起成長。
作者:程序猿學社
原創公衆號:『程序猿學社』,專注於java技術棧,分享java各個技術系列專題,以及各個技術點的面試題。
原創不易,轉載請註明來源(註明:來源於公衆號:程序猿學社, 作者:程序猿學社)。