前言
喜歡看技術(shù)性的文章,記得以前總是說(shuō)設(shè)計(jì)模式不用太早接觸之類的話, 促使我決定好好學(xué)習(xí)設(shè)計(jì)模式的原因
- 現(xiàn)在我覺得時(shí)機(jī)也差不多了
- 實(shí)在有些代碼寫完之后發(fā)現(xiàn)很難維護(hù),或者改動(dòng)的地方特別多, 肯定是我當(dāng)時(shí)的設(shè)計(jì)有問(wèn)題
所發(fā)表所有內(nèi)容僅代表個(gè)人觀點(diǎn)。
設(shè)計(jì)模式概念
說(shuō)到設(shè)計(jì)模式,第一反應(yīng)就是很深?yuàn)W畦幢,完全理解不了這個(gè)概念到底是什么意思, 網(wǎng)上的定義最多的是: 設(shè)計(jì)模式是一套被反復(fù)使用的廷没、多數(shù)人知曉的、經(jīng)過(guò)分類編目的轴术、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)议谷。使用設(shè)計(jì)模式是為了重用代碼、讓代碼更容易被他人理解蔬浙、保證代碼可靠性。
反復(fù)使用
, 多人知曉
: 意味著被眾人認(rèn)可
分類編目
: 歸納和總結(jié)特征進(jìn)行分類
代碼設(shè)計(jì)經(jīng)驗(yàn)
: 經(jīng)前人驗(yàn)證過(guò)的, 保證好使的套路
當(dāng)然, 其實(shí)這都是個(gè)人理解的不同而不同, 權(quán)威的概念:
設(shè)計(jì)模式(Design pattern)代表了最佳的實(shí)踐贞远,通常被有經(jīng)驗(yàn)的面向?qū)ο蟮能浖_發(fā)人員所采用畴博。設(shè)計(jì)模式是軟件開發(fā)人員在軟件開發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案。這些解決方案是眾多軟件開發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的蓝仲。設(shè)計(jì)模式是一套被反復(fù)使用的俱病、多數(shù)人知曉的官疲、經(jīng)過(guò)分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)亮隙。使用設(shè)計(jì)模式是為了重用代碼途凫、讓代碼更容易被他人理解、保證代碼可靠性溢吻。毫無(wú)疑問(wèn)维费,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的,設(shè)計(jì)模式使代碼編制真正工程化促王,設(shè)計(jì)模式是軟件工程的基石犀盟,如同大廈的一塊塊磚石一樣。項(xiàng)目中合理地運(yùn)用設(shè)計(jì)模式可以完美地解決很多問(wèn)題硼砰,每種模式在現(xiàn)實(shí)中都有相應(yīng)的原理來(lái)與之對(duì)應(yīng)且蓬,每種模式都描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問(wèn)題,以及該問(wèn)題的核心解決方案题翰,這也是設(shè)計(jì)模式能被廣泛應(yīng)用的原因恶阴。
為何學(xué)習(xí)設(shè)計(jì)模式
就像開篇我所說(shuō)的, 當(dāng)你發(fā)現(xiàn)一個(gè)新的需求,或者小的需求變更, 難以更改以前的代碼, 有木有種牽一發(fā)而動(dòng)全身的感覺?有? 那你跟我一樣, 該好好學(xué)學(xué)設(shè)計(jì)模式了, 同學(xué),其他不用多講了, 還需要其他理由就自行百度吧.
設(shè)計(jì)模式的六大原則
單一職責(zé)
描述的意思是每個(gè)類都只負(fù)責(zé)單一的功能,切不可太多豹障,并且一個(gè)類應(yīng)當(dāng)盡量的把一個(gè)功能做到極致冯事。
這個(gè)我參考了一些資料, 發(fā)現(xiàn)怎么寫的都有, 還要根據(jù)項(xiàng)目實(shí)際情況考慮,也不能說(shuō)過(guò)分的拆分職責(zé).
建議:
接口一定要做到單一職責(zé),類的設(shè)計(jì)盡量做到只有一個(gè)原因引起變化
例子:
我看到一個(gè)例子, 簡(jiǎn)單描述:
public interface IPhone {
public void 撥號(hào)(string number)
public void 接電話()
public void 通話()
public void 應(yīng)答()
public void 掛斷()
}
原文解釋:
撥號(hào) ()
和掛斷 ()
兩個(gè)方法實(shí)現(xiàn)的是協(xié)議管理血公,分別負(fù)責(zé)撥號(hào)接通和掛機(jī)昵仅;通話 ()
和應(yīng)答 ()
是數(shù)據(jù)的傳送,把我們說(shuō)的話轉(zhuǎn)換成模擬信號(hào)或數(shù)字信號(hào)傳遞到對(duì)方累魔,然后再把對(duì)方傳遞過(guò)來(lái)的信號(hào)還原成我們聽得懂語(yǔ)言摔笤。我們可以這樣考慮這個(gè)問(wèn)題,協(xié)議接通的變化會(huì)引起這個(gè)接口或?qū)崿F(xiàn)類的變化嗎垦写?會(huì)的吕世!那數(shù)據(jù)傳送(想想看,電話不僅僅可以通話梯投,還可以上網(wǎng))的變化會(huì)引起這個(gè)接口或?qū)崿F(xiàn)類的變化嗎命辖?會(huì)的!那就很簡(jiǎn)單了分蓖,這里有兩個(gè)原因都引起了類的變化尔艇,而且這兩個(gè)職責(zé)會(huì)相互影響嗎?電話撥號(hào)么鹤,我只要能接通就成终娃,甭管是電信的還是網(wǎng)通的協(xié)議;電話連接后還關(guān)心傳遞的是什么數(shù)據(jù)嗎午磁?不關(guān)心尝抖,你要是樂意使用56K的小貓傳遞一個(gè)高清的片子毡们,那也沒有問(wèn)題迅皇。通過(guò)這樣的分析昧辽,我們發(fā)現(xiàn)類圖上的電話 接口包含了兩個(gè)職責(zé),而且這兩個(gè)職責(zé)的變化不相互影響登颓,那就考慮拆開成兩個(gè)接口搅荞,然后把這個(gè)接口拆成兩個(gè)接口, 一個(gè)負(fù)責(zé)數(shù)據(jù)傳輸, 一個(gè)負(fù)責(zé)接與掛電話...
我認(rèn)為這個(gè)說(shuō)法也不完全正確, 像這種情況, 設(shè)計(jì)一個(gè)成一個(gè)"電話相關(guān)的接口, 也不是不對(duì)的,它具有這些方法, 很正常. 所以這東西仁者見仁, 智者見智!
單一職責(zé)原則提出了一個(gè)編寫程序的標(biāo)準(zhǔn),用“職責(zé)”或“變化原因”來(lái)衡量接口或類設(shè)計(jì)得是否合理框咙,但是“職責(zé)”和“變化原因”都是沒有具體標(biāo)準(zhǔn)的咕痛,一個(gè)類到底要負(fù)責(zé)那些職責(zé)?這些職責(zé)怎么細(xì)化喇嘱?細(xì)化后是否都要有一個(gè)接口或類茉贡?這些都需從實(shí)際的情況考慮。因項(xiàng)目而異者铜,因環(huán)境而異腔丧。
總結(jié):
這應(yīng)該是很簡(jiǎn)單的一個(gè)原則了, 同時(shí)也是很模糊的一個(gè)原則, 需要經(jīng)驗(yàn), 環(huán)境, 團(tuán)隊(duì)協(xié)作等很多方面的考慮, 所以原則是死的 , 人是活的! !!! 盡量遵守!
里氏替換原則
這個(gè)原則表達(dá)的意思是一個(gè)子類應(yīng)該可以替換掉父類并且可以正常工作。同樣的,用基類替換現(xiàn)在的子類作烟,必須保證程序運(yùn)行(包括業(yè)務(wù)流程)都是不變的愉粤。這是我看了一些資料后的理解
例子:
首先設(shè)計(jì)一個(gè)Person類 人類, 具有很多能力, 包括生育, 但僅限女性對(duì)吧
public class Person {
// 生育 需要傳入一個(gè)女人對(duì)象
public void bearChildren(Woman woman) {
woman.bearChildren();
}
}
再設(shè)計(jì)一個(gè)Woman 女人對(duì)象和更細(xì)致一點(diǎn)的Girl 女孩對(duì)象, 女孩對(duì)象繼承女人對(duì)象
public class Woman {
public void bearChildren() {
System.out.println("順利生產(chǎn)");
}
}
public class Girl extends Woman {
// 重寫了父類的生育方法
@Override
public void bearChildren() {
throw new RuntimeErrorException(null, "我還是小孩,無(wú)法生孩子");
}
}
運(yùn)行Person類的生育方法
public class App {
public static void main(String[] args) {
Person person = new Person();
person.bearChildren(new Woman());
person.bearChildren(new Girl());
}
}
運(yùn)行結(jié)果:
woman 順利生產(chǎn)
girl Exception in thread "main" javax.management.RuntimeErrorException: 我還是小孩,無(wú)法生孩子
這個(gè)異常是運(yùn)行時(shí)才會(huì)產(chǎn)生的,也就是說(shuō)拿撩,Person類并不知道會(huì)出現(xiàn)這種情況衣厘,Woman傳給Person.bearChildRen完成生育功能,Girl類繼承了Woman压恒,當(dāng)然也可以了實(shí)現(xiàn)同樣功能影暴,但是最終這個(gè)調(diào)用會(huì)拋出異常。
注意:
一個(gè)子類應(yīng)該可以替換掉父類并且可以正常工作, 那么現(xiàn)在替換后程序沒有正常工作, 所以違背了里氏替換原則
這項(xiàng)原則并不是要避免多態(tài)探赫,而是要求子類在繼承父類的時(shí)候型宙,不能與父類已經(jīng)定好的契約沖突,也就是不要重寫父類已經(jīng)實(shí)現(xiàn)的方法期吓。
簡(jiǎn)而言之:子類必須可以替換成父類對(duì)象(并且行為不會(huì)變)早歇,子類不要添加基類沒有的約束,那么怎么檢驗(yàn)是否符合這個(gè)原則呢?就是用基類替換現(xiàn)在的子類讨勤,必須保證程序運(yùn)行(包括業(yè)務(wù)流程)都是不變的箭跳。
子類不能添加父類沒有的約束的例子:
比如場(chǎng)景是讓汽車奔跑,子類是1.奔馳(前提是加100L汽油)2.寶馬(前提要洗車才能跑)潭千。
問(wèn)題來(lái)了谱姓,不同的子類有了不同的約束條件。
public abstract class Car {
public abstract void Run();
}
public class Benz : Car {
public int Oil;
public overrride void Run() {
//跑
}
}
public class BMW: Car {
public bool WashCar;
public overrride void Run() {
//跑
}
}
下面有一個(gè)工廠方法(目的是體現(xiàn)用基類替換子類刨晴,因?yàn)楣S方法返回值是父類類型)
public Class Factory {
public static Car Create() {
if(1) return new Benz();
if(2) return new BMW();
}
}
最后是實(shí)際調(diào)用代碼:
來(lái)一輛奔馳:Car Benz=Factory.Create(1)
;
來(lái)一輛寶馬:Car BMW=Factory.Create(2)
;
然后我準(zhǔn)備開車了....
Benz.Run();
BMW.Run();
您覺得能行嗎屉来?肯定不行路翻,還沒加油呢,還沒洗車呢茄靠。
我前面還得先
Benz.Oil=100;
BMW.WashCar=true;
才能
Benz.Run();
BMW.Run();
但是明顯這樣就違背父類預(yù)期的原則了茂契,怎么辦?其實(shí)可以把參數(shù)放到子類構(gòu)造函數(shù)的形參中慨绳。解決問(wèn)題方案有多種掉冶,但原則就是”子類必須可以替換成父類對(duì)象(并且行為不會(huì)變),子類不要添加基類沒有的約束"
依賴倒轉(zhuǎn)原則
針對(duì)接口編程脐雪,依賴于抽象而不依賴于具體厌小。
例子
我現(xiàn)在需要從一個(gè)文本文件中獲取變量number的值, 有如下類
public class Reader {
public static int getNumber(String path){
BufferedReader br = new BufferedReader(new FileReader(new File(path)));
return Integer.valueOf(br.readLine())''
}
}
我調(diào)用 Reader.getNumber("file.text")
獲取到了number的值
那么如果需求變更了, 需要從數(shù)據(jù)庫(kù)中獲取這個(gè)number的值呢? 從XML文件中獲取呢? 我需要改Reader getNumber的方法,或者我需要加一堆方法, getNumberFormXMl... getNumberFormDB?
不如直接寫一個(gè)接口
public interface Redaer {
public int getNumber();
}
用不同的實(shí)現(xiàn)getNumber
即可. 你可以有XMLReader
, 或者DBReader
來(lái)實(shí)現(xiàn)getNumber
,客戶端只管Reader.getNumber()
就好, 無(wú)需關(guān)心number是怎么來(lái)的.
接口隔離原則
也稱接口最小化原則,強(qiáng)調(diào)的是一個(gè)接口擁有的行為應(yīng)該盡可能的小战秋。
例子:
比如我們?cè)O(shè)計(jì)一個(gè)手機(jī)的接口時(shí)璧亚,就要手機(jī)哪些行為是必須的,要讓這個(gè)接口盡量的小脂信,或者通俗點(diǎn)講癣蟋,就是里面的行為應(yīng)該都是這樣一種行為,就是說(shuō)只要是手機(jī)吉嚣,你就必須可以做到的梢薪。
public interface Mobile {
public void call();//手機(jī)可以打電話
public void sendMessage();//手機(jī)可以發(fā)短信
public void playBird();//手機(jī)可以玩憤怒的小鳥?
}
上面第三個(gè)行為明顯就不是一個(gè)手機(jī)應(yīng)該有的尝哆,或者說(shuō)不是一個(gè)手機(jī)必須有的秉撇,那么上面這個(gè)手機(jī)的接口就不是最小接口,假設(shè)我現(xiàn)在的非智能手機(jī)去實(shí)現(xiàn)這個(gè)接口秋泄,那么playBird方法就只能空著了琐馆,因?yàn)樗荒芡妗K晕覀兏玫淖龇ㄊ侨サ暨@個(gè)方法恒序,讓Mobile接口最小化瘦麸,然后再建立下面這個(gè)接口去擴(kuò)展現(xiàn)有的Mobile接口。
public interface SmartPhone extends Mobile{
public void playBird();//智能手機(jī)的接口就可以加入這個(gè)方法了
}
這樣兩個(gè)接口就都是最小化的了歧胁,這樣我們的非智能手機(jī)就去實(shí)現(xiàn)Mobile接口滋饲,實(shí)現(xiàn)打電話和發(fā)短信的功能,而智能手機(jī)就實(shí)現(xiàn)SmartPhone接口喊巍,實(shí)現(xiàn)打電話屠缭、發(fā)短信以及玩憤怒的小鳥的功能,兩者都不會(huì)有多余的要實(shí)現(xiàn)的方法崭参。
還有一個(gè)例子:
比如電商項(xiàng)目的訂單系統(tǒng),看看別人是怎么設(shè)計(jì)的
訂單這個(gè)類,有兩個(gè)地方會(huì)使用到,一般是前臺(tái)門戶,用戶查詢訂單, 后臺(tái)管理, 管理員處理訂單, 增刪改查, 其實(shí)門戶也可以增刪改查訂單的,有些系統(tǒng),這里就假設(shè)門戶只能查詢訂單, 有其他也是同樣道理
interface IOrderForPortal{ 門戶接口
String getOrder();
}
interface IOrderForAdmin{ 后臺(tái)接口
String deleteOrder();
String updateOrder();
String insertOrder();
String getOrder();
}
/*
// 如果門戶和后臺(tái)查詢訂單方法相同實(shí)現(xiàn), 也可以這樣
interface IOrderForPortal{
String getOrder();
}
interface IOrderForAdmin extendsIOrderForPortal{
String updateOrder();
String deleteOrder();
}
*/
class Order implements IOrderForPortal,IOrderForAdmin{
private Order(){
//--什么都不干,就是為了不讓直接 new,防止客戶端直接New,然后訪問(wèn)它不需要的方法.
}
//返回給Portal
public static IOrderForPortal getOrderForPortal(){
return (IOrderForPortal)new Order();
}
//返回給Admin
public static IOrderForAdmin getOrderForAdmin(){
return (IOrderForAdmin)new Order();
}
//--下面是接口方法的實(shí)現(xiàn).只是返回了一個(gè)String用于演示
public String getOrder(){
return "implemented getOrder";
}
public String insertOrder(){
return "implementedinsertOrder";
}
public String updateOrder(){
return "implementedupdateOrder";
}
public String deleteOrder(){
return "implementeddeleteOrder";
}
}
public class TestCreateLimit{
public static void main(String[]
args){
IOrderForPortal orderForPortal =Order.getOrderForPortal();
IOrderForAdmin orderForAdmin = Order.getOrderForAdmin();
System.out.println("Portal門戶調(diào)用方法:"+orderForPortal.getOrder());
System.out.println("Admin管理后臺(tái)調(diào)用方法:"+orderForAdmin.getOrder()+";"+orderForAdmin.insertOrder()+";"+orderForAdmin.updateOrder()+";"+orderForAdmin.deleteOrder());
}
}
這樣就能很好的滿足接口隔離原則了,調(diào)用者只能訪問(wèn)它自己的方法,不能訪問(wèn)到不應(yīng)該訪問(wèn)的方法.
很多人會(huì)覺的接口隔離原則跟之前的單一職責(zé)原則很相似呵曹,其實(shí)不然。其一,單一職責(zé)原則原注重的是職責(zé)奄喂;而接口隔離原則注重對(duì)接口依賴的隔離铐殃。其二,單一職責(zé)原則主要是約束類跨新,其次才是接口和方法富腊,它針對(duì)的是程序中的實(shí)現(xiàn)和細(xì)節(jié);而接口隔離原則主要約束接口接口玻蝌,主要針對(duì)抽象蟹肘,針對(duì)程序整體框架的構(gòu)建词疼。
采用接口隔離原則對(duì)接口進(jìn)行約束時(shí)俯树,要注意以下幾點(diǎn):
- 接口盡量小,但是要有限度贰盗。對(duì)接口進(jìn)行細(xì)化可以提高程序設(shè)計(jì)靈活性是不掙的事實(shí)许饿,但是如果過(guò)小,則會(huì)造成接口數(shù)量過(guò)多舵盈,使設(shè)計(jì)復(fù)雜化陋率。所以一定要適度。
- 為依賴接口的類定制服務(wù)秽晚,只暴露給調(diào)用的類它需要的方法瓦糟,它不需要的方法則隱藏起來(lái)。只有專注地為一個(gè)模塊提供定制服務(wù)赴蝇,才能建立最小的依賴關(guān)系菩浙。
- 提高內(nèi)聚,減少對(duì)外交互句伶。使接口用最少的方法去完成最多的事情劲蜻。
運(yùn)用接口隔離原則,一定要適度考余,接口設(shè)計(jì)的過(guò)大或過(guò)小都不好先嬉。設(shè)計(jì)接口的時(shí)候,只有多花些時(shí)間去思考和籌劃楚堤,才能準(zhǔn)確地實(shí)踐這一原則疫蔓。
迪米特法則,又稱最少知道原則
最少知道原則是指:一個(gè)實(shí)體應(yīng)當(dāng)盡量少地與其他實(shí)體之間發(fā)生相互作用身冬,使得系統(tǒng)功能模塊相對(duì)獨(dú)立
這個(gè)原則的制定衅胀,是因?yàn)槿绻粋€(gè)類知道或者說(shuō)是依賴于另外一個(gè)類太多細(xì)節(jié),這樣會(huì)導(dǎo)致耦合度過(guò)高吏恭,應(yīng)該將細(xì)節(jié)全部高內(nèi)聚于類的內(nèi)部拗小,其他的類只需要知道這個(gè)類主要提供的功能即可。
所謂高內(nèi)聚就是盡可能將一個(gè)類的細(xì)節(jié)全部寫在這個(gè)類的內(nèi)部樱哼,不要漏出來(lái)給其他類知道哀九,否則其他類就很容易會(huì)依賴于這些細(xì)節(jié)剿配,這樣類之間的耦合度就會(huì)急速上升,這樣做的后果往往是一個(gè)類隨便改點(diǎn)東西阅束,依賴于它的類全部都要改呼胚。
開閉原則
就是說(shuō)我任何的改變都不需要修改原有的代碼,而只需要加入一些新的實(shí)現(xiàn)息裸,就可以達(dá)到我的目的蝇更,這是系統(tǒng)設(shè)計(jì)的理想境界,但是沒有任何一個(gè)系統(tǒng)可以做到這一點(diǎn)呼盆,哪怕我一直最欣賞的spring框架也做不到年扩,雖說(shuō)它的擴(kuò)展性已經(jīng)強(qiáng)到變態(tài)。
這個(gè)原則更像是前五個(gè)原則的總綱访圃,前五個(gè)原則就是圍著它轉(zhuǎn)的厨幻,只要我們盡量的遵守前五個(gè)原則,那么設(shè)計(jì)出來(lái)的系統(tǒng)應(yīng)該就比較符合開閉原則了腿时,相反况脆,如果你違背了太多,那么你的系統(tǒng)或許也不太遵循開閉原則批糟。
參考:
感謝這些作者為我們這些后來(lái)者提供了大量的閱讀資料, 還有一些百度查到的零碎的資料
http://www.cnblogs.com/zuoxiaolong/p/pattern1.html
http://www.reibang.com/p/60aea0cf6bda