文章中涉及到的代碼,可到這里來拿:https://gitee.com/daijiyong/DesignPattern
原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象,同時又能保證性能
調(diào)用者不需要知道任何創(chuàng)建細(xì)節(jié),不調(diào)用構(gòu)造函數(shù)
屬于創(chuàng)建型模式
說白了就是克隆,其核心就是拷貝模型,減少拷貝對象的復(fù)雜度,提高性能和可維護(hù)性
舉個栗子,有這么一個類
public class ExamPaper {
private String examinationPaperId;//試卷主鍵
private String leavTime;//剩余時間
private String organizationId;//單位主鍵
private String id;//考試主鍵
private String examRoomId;//考場主鍵
private String userId;//用戶主鍵
private String specialtyCode;//專業(yè)代碼
private String postionCode;//報考崗位
private String gradeCode;//報考等級
private String examStartTime;//考試開始時間
private String examEndTime;//考試結(jié)束時間
private String singleSelectionImpCount;//單選選題重要數(shù)量
private String multiSelectionImpCount;//多選題重要數(shù)量
private String judgementImpCount;//判斷題重要數(shù)量
private String examTime;//考試時長
private String fullScore;//總分
private String passScore;//及格分
private String userName;//學(xué)員姓名
private String score;//考試得分
private String resut;//是否及格
private String singleOkCount;//單選題答對數(shù)量
private String multiOkCount;//多選題答對數(shù)量
private String judgementOkCount;//判斷題答對數(shù)量
//構(gòu)造方法
//get、set方法
}
這個類的成員變量特別多,如果手動創(chuàng)建的話需要寫大量的代碼
如果創(chuàng)建相同對象的需求也比較多
那該怎么辦呢?
我們可以再這個類中新增一個clone的方法
public ExamPaper copy() {
ExamPaper examPaper = new ExamPaper();
//剩余時間
examPaper.setLeavTime(this.getLeavTime());
//單位主鍵
examPaper.setOrganizationId(this.getOrganizationId());
//考試主鍵
examPaper.setId(this.getId());
//用戶主鍵
examPaper.setUserId(this.getUserId());
//專業(yè)
examPaper.setSpecialtyCode(this.getSpecialtyCode());
//崗位
examPaper.setPostionCode(this.getPostionCode());
//等級
examPaper.setGradeCode(this.getGradeCode());
//考試開始時間
examPaper.setExamStartTime(this.getExamStartTime());
//考試結(jié)束時間
examPaper.setExamEndTime(this.getExamEndTime());
//單選題重要數(shù)量
examPaper.setSingleSelectionImpCount(this.getSingleSelectionImpCount());
//多選題重要數(shù)量
examPaper.setMultiSelectionImpCount(this.getMultiSelectionImpCount());
//判斷題重要數(shù)量
examPaper.setJudgementImpCount(this.getJudgementImpCount());
//考試時間
examPaper.setExamTime(this.getExamTime());
//總分
examPaper.setFullScore(this.getFullScore());
//及格分
examPaper.setPassScore(this.getPassScore());
//學(xué)員姓名
examPaper.setUserName(this.getUserName());
//分?jǐn)?shù)
examPaper.setScore(this.getScore());
//單選答對數(shù)量
examPaper.setSingleOkCount(this.getSingleOkCount());
//多選答對數(shù)量
examPaper.setMultiOkCount(this.getMultiOkCount());
//判斷答對數(shù)量
examPaper.setJudgementOkCount(this.getJudgementOkCount());
return examPaper;
}
這樣在復(fù)制對象的時候就可以不那么繁瑣了
但是這樣維護(hù)和看起來還是太過笨重了,不夠優(yōu)雅
想到復(fù)制對象無非就是遍歷這個對象的所有成員屬性
很容易就能聯(lián)想到Java的反射機(jī)制
我們就可以利用Java的反射機(jī)制寫一個工具類用來復(fù)制對象
1. Bean復(fù)制工具類
public class BeanUtils {
/**
* 通過反射機(jī)制實現(xiàn)遍歷屬性并賦值,克隆對象
*
* @param prototype 對象
* @return 新對象
*/
public static Object copy(Object prototype) {
Class clazz = prototype.getClass();
Object returnValue = null;
try {
returnValue = clazz.newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
field.set(returnValue, field.get(prototype));
}
} catch (Exception e) {
e.printStackTrace();
}
return returnValue;
}
}
然后再使用的時候就可以這樣調(diào)用了
看起來就不那么繁瑣了
寫一個測試類測試一下
public class Test {
public static void main(String[] args) {
ExamPaper examPaper = new ExamPaper();
System.out.println(examPaper);
ExamPaper examPaperCopy = (ExamPaper) BeanUtils.copy(new ExamPaper());
System.out.println(examPaperCopy);
}
}
結(jié)果肯定復(fù)制成功
但是這樣其實是有一個問題的,我們下面再說
先說一下如果使用原型模式,怎么實現(xiàn)對象的復(fù)制
2. 一般形式
首先我們需要創(chuàng)建一個接口
并定義一個clone()的方法
public interface Iprototype<T> {
T clone();
}
然后創(chuàng)建一個類,并實現(xiàn)這個原型模式方法
public class Man implements IPrototype {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Man clone() {
//在這里邊寫復(fù)制本對象的邏輯
//可以這樣手動創(chuàng)建
Man concretePrototype = new Man();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
return concretePrototype;
//也可以使用這種方式也行
// return (ConcretePrototype) BeanUtils.copy(this);
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
寫一個測試類測試一下
“完美”克隆
這就是原型模式的一般形式
Java本身也帶有一個Cloneable的接口
3. 淺克隆
進(jìn)入Cloneable可以看到這個接口沒有定義任何方法
clone()方法是定義在了Object類中
在上邊例子的基礎(chǔ)上,實現(xiàn)Java的Cloneable接口
并重寫調(diào)用Object類的clone()接口
@Override
public Man clone() {
try {
return (Man) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
然后再加一個成員屬性friends
使用List的類型
private List<String> friends;
然后再寫一個測試類測試一下
public class Test {
public static void main(String[] args) {
//創(chuàng)建原型對象
Man man = new Man();
man.setAge(18);
man.setName("daijiyong");
List<String> friends = new ArrayList<String>();
friends.add("張三");
friends.add("李四");
man.setFriends(friends);
//拷貝原型對象
Man cloneMan = man.clone();
cloneMan.getFriends().add("王五");
System.out.println(man);
System.out.println(cloneMan);
}
}
然后打印結(jié)果卻發(fā)現(xiàn)一個問題
我是對賦值后的man對象添加了一個朋友
并將年齡修改為了20歲
根據(jù)打印的結(jié)果
發(fā)現(xiàn)原始對象的年齡沒有變化
但是朋友卻跟著也增加了
這就是因為淺克隆的原因
當(dāng)我們使用基本數(shù)據(jù)類型進(jìn)行直接賦值操作的時候
原對象和復(fù)制對象的值是相互不影響的
但是,如果是非基本數(shù)據(jù)類型,則會是引用賦值
本本質(zhì)就是僅僅是復(fù)制了一個指針,指向的實際對象是同一個
那么如何實現(xiàn)復(fù)制出來的對象時一個全新的對象,跟原對象沒有任何關(guān)系呢?
這個時候就引出另外一個概念:深克隆
4. 深克隆
實現(xiàn)深克隆一般有兩個方法
利用json的方式
引入json的工具類
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.32</version>
</dependency>
新增json深克隆方法
public Man deepCloneJson() {
try {
String json = JSONObject.toJSONString(this);
return JSONObject.parseObject(json, Man.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
修改測試類進(jìn)行測試
完美解決
序列化的方式
如果一個對象想實現(xiàn)序列化,畢業(yè)要實現(xiàn)Serializable接口
public class Man implements Cloneable, Serializable {
//......
}
新增一個通過序列化深克隆的方法
public Man deepCloneSerialise() {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Man) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (bos != null) {
bos.close();
}
if (oos != null) {
oos.close();
}
if (bis != null) {
bis.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改測試類,并執(zhí)行
完美解決
推薦使用序列化的方式實現(xiàn)深克隆
5. 源碼實現(xiàn)
Java底層有些類也重寫了clone()方法,實現(xiàn)深克隆
比如ArrayList的clone()方法其實就是深克隆
我們都知道ArrayList的底層是基于數(shù)組實現(xiàn)
這個clone()方法就是在淺克隆的基礎(chǔ)上
使用數(shù)組又新建了一個新的數(shù)組
并將新數(shù)組的內(nèi)容復(fù)制給當(dāng)前ArrayList,返回
6. 問題
原型模式的基本原理和使用方法基本都明白了
但是有一個致命的問題,如果使用原型模式去創(chuàng)建單例模式的對象
豈不是會破壞單例模式對象的唯一性?
其實這個問題也非常好解決
辦法就是,如果是單例模式的類,那就不要實現(xiàn)克隆接口,不要提供clone()方法
你都單例了就為什么還要實現(xiàn)原型模式接口呢?
這樣就能從源頭上解決這個問題了
7. 總結(jié)
使用場景:
類的初始化消耗資源較多
new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準(zhǔn)備,訪問權(quán)限)等
構(gòu)造函數(shù)比較復(fù)雜
循環(huán)體中產(chǎn)生大量對象
優(yōu)點:
性能優(yōu)良,Java自帶的原型模式是基于內(nèi)存二進(jìn)制流的拷貝,比直接new一個對象性能上提升了很多
可以是用深克隆方式保存對象的狀態(tài),使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來,簡化創(chuàng)建過程
缺點:
必須配備克隆方法
違反了開閉原則
深拷貝和淺拷貝運(yùn)用需要得當(dāng)
文/戴先生@2020年7月29日
---end---