什么是原型模式翘骂?為什么要使用原型模式?
前兩天面試了一個95年碩士畢業(yè)的小姐姐,在杭州某大廠工作了兩年葫松,最近想回家鄉(xiāng)發(fā)展
對于兩年以上工作經驗的候選人,我都會問一些和設計模式相關的面試題
不得不面對一個現(xiàn)實底洗,大部分候選人對設計模式都沒有很深入的理解腋么,回答的并不出彩
當我對這個小姐姐提出這兩個問題時,也沒抱有很高的期望亥揖。沒想到小姐姐的回答很讓人意外珊擂,甚至可以說是讓我對原型模式有了更深刻的理解
為什么要使用原型模式
假如有一個類,命名為A费变。A類里面有兩個屬性摧扇,分別是x和y,并為這兩個屬性提供對應的get和set方法
將這個類的實體對象a作為test方法的參數(shù)
要求在test方法內利用a對象的某些屬性進行一些業(yè)務邏輯處理挚歧,但不能改變a對象的原有屬性
我們進行第一次嘗試:聲明一個新的對象a1扛稽,并把a賦值給它。讓test方法利用a1對象的屬性進行業(yè)務邏輯處理
publicstaticvoidtest(A?a){
A?a1?=?a;
System.out.println("test方法開始業(yè)務邏輯處理");
a1.setX(1);
}
我們來驗證一下是否會影響到a對象的屬性
publicstaticvoidmain(String[]?args){
A?a?=newA();
a.setX(0);
System.out.println("調用test方法前x="+?a.getX());
test(a);
System.out.println("調用test方法后x="+?a.getX());
}
輸出結果為
從輸出結果來看滑负,test方法改變了a對象的屬性在张,不符合要求。所以矮慕,第一次嘗試失敗
其實也不難理解帮匾,我們都知道JVM加載對象后會給對象分配內存空間
加載完a之后,給a分配一個空間
在加載a1的時候痴鳄,因為a1是將a的值賦值給了a1瘟斜,所以在給a1分配空間時,只是把a1的引用指向了a所在的內存地址,并沒有給a1分配獨立的內存空間
所以修改a1對象的屬性時哼转,a對象也會被改變
我們調整思路進行第二次嘗試:重新new一個新對象a2明未,把a對象的所有屬性值賦值給a2。test方法利用a2對象進行業(yè)務邏輯處理
publicstaticvoidtest(A?a){
A?a2?=newA();
a2.setX(a.getX());
a2.setY(a.getY());
System.out.println("test方法開始業(yè)務邏輯處理");
a2.setX(1);
a2.setY(2);
}
同樣來驗證一下是否會影響到a對象的屬性
publicstaticvoidmain(String[]?args){
A?a?=newA();
a.setX(0);
a.setY(0);
System.out.println("調用test方法前x="+?a.getX()?+"壹蔓,y="+?a.getY());
test(a);
System.out.println("調用test方法后x="+?a.getX()?+"趟妥,y="+?a.getY());
}
輸出結果為
這次的輸出結果顯示,test方法并沒有改變a對象的屬性佣蓉,符合要求
但是披摄,有一個問題
如果a不是一個具體的實例,而是一個抽象類或者接口勇凭。抽象類或者接口是不能被new的疚膊,該怎么辦?
這時候就要使用到原型模式來解決我們的問題了
原型模式
原型模式定義
「原型模式」可以讓你復制或克隆一個已有對象虾标,而又無需使你的代碼依賴這個對象所屬的類
通過定義我們可以提取出來兩個關鍵信息
第一寓盗,原型模式主要作用是復制或克隆一個已有對象
第二,去復制這個對象時不需要依賴這個對象所屬的類
這句話很有意思璧函,想要創(chuàng)建一個對象但是不用依賴這個對象所屬的類傀蚌,這要怎么實現(xiàn)?
答案就是把創(chuàng)建對象的過程交給這個類來處理
原型模式實戰(zhàn)
我們用原型模式來優(yōu)化一下上面的例子
動手之前我們需要知道原型模式的設計思路
根據(jù)定義可以知道在原型模式中蘸吓,對象的創(chuàng)建過程是交給對象所屬的類來處理的善炫,所以這個類肯定要提供一個方法,方法的返回值是這個對象库继。通常這個方法叫clone()或copy()
套用到上面例子的A類中箩艺,需要在A類里面提供一個clone()方法,在方法中創(chuàng)建一個當前對象并返回
在test方法中利用clone()來獲取一個a3對象去執(zhí)行業(yè)務邏輯
publicstaticvoidtest(A?a){
A?a3?=?a.clone();
System.out.println("test方法開始業(yè)務邏輯處理");
a3.setX(1);
}
再驗證一下是否改變了a對象的屬性
從輸出結果可以看到是沒有改變a對象的屬性的
那我們再來解決上面例子中遇到的問題宪萄,假如A是一個抽象類艺谆,該怎么去創(chuàng)建這個對象
其實也很簡單,抽象類中是可以有抽象方法的拜英。把clone()方法定義為抽象方法静汤,讓子類去實現(xiàn)它
假如A有兩個子類,分別是SubA1和SubA2聊记。兩個子類分別繼承A抽象類,并實現(xiàn)clone()抽象方法
在test中還是使用a.clone()就可以得到一個新的對象恢暖,而且不會影響到原有的a對象
這就用原型模式對上面的例子完成了優(yōu)化
深拷貝排监、淺拷貝
在java中,默認Object類是所有類的父類杰捂,在Object中有一個clone()方法
它是java默認提供的用來復制對象的方法舆床,這個方法是native修飾的,說明它是對操作系統(tǒng)的底層直接調用的,在理論上挨队,用它來復制對象性能會更好
所以谷暮,我們可以使用java.lang.Object#clone()來實現(xiàn)原型模式,總共分為兩步
被復制的類需要實現(xiàn)Cloneable這個接口類盛垦。這個接口類里面是沒有任何一個方法的湿弦,只是起到一個標記作用,也可以理解成一種約定(「約定大于配置」)
被復制的類需要重寫Object中的clone()方法
下面我們就來優(yōu)化一下A類
這樣寫出的原型模式腾夯,在理論上執(zhí)行效率更高颊埃。看似完美蝶俱,實則不然
假如A類里面有一個ArrayList屬性
我們來看一下班利,在clone完a后得到a4,改變a4的list屬性榨呆,會不會對a造成影響
輸出結果為
在修改a4對象時罗标,也改變了a對象的屬性值,這不是我們期望的結果
這是因為:Object在clone時只會對基礎類型的數(shù)據(jù)進行拷貝积蜻,引用類型的數(shù)據(jù)并沒有真正的拷貝闯割,而是把引用指針指向了這個數(shù)據(jù)在內存中的地址(還記得上文中a和a1指向同一個內存地址的例子嗎)
這種只拷貝基礎數(shù)據(jù)類型的行為,我們稱之為淺拷貝浅侨。既可以拷貝基礎數(shù)據(jù)類型纽谒,又可以拷貝引用數(shù)據(jù)類型的行為,我們稱之為深拷貝
在原型模式中如输,我們應該使用深拷貝來復制對象鼓黔。
要實現(xiàn)深拷貝,「需要這個引用類型的數(shù)據(jù)所屬的類也實現(xiàn)Cloneable接口不见,并且重寫Object類的clone()方法」
在本例子中澳化,引用類型所屬的類是ArrayList,它本身已經實現(xiàn)了Cloneable接口稳吮,并重寫了Object類的clone()方法缎谷。
我們只需要在A類的clone()方法中調用ArrayList的clone()方法即可
這樣就基于深拷貝完成了原型模式
總結
「原型模式」也叫「克隆模式」,它屬于設計模式三大類型中的創(chuàng)建型模式
在你需要復制一個對象灶似,而又不希望改變原有對象的時候可以考慮使用原型模式來實現(xiàn)
在實現(xiàn)原型模式時列林,引用類型數(shù)據(jù)的復制要基于深拷貝,否則會影響到被拷貝的原型
在Spring生態(tài)下酪惭,對象的創(chuàng)建基本都由IOC來實現(xiàn)希痴,原型模式好像沒有多少用武之地
但是,用的少不代表沒用春感。我們在學習設計模式時砌创,目的不僅僅在于要學會設計模式虏缸,而是要學會設計模式使用的設計思想
學會這種思想,沉淀為自己的思路嫩实,在工作中能實現(xiàn)舉一反三刽辙,才能無往不利
-- 以上內容來自公眾號「赫連小伍」,轉載請注明出處