開篇:設(shè)計(jì)模式六大原則

設(shè)計(jì)模式也反復(fù)看了一段日子了,但是疏于使用的話壕翩,時(shí)間久了還是會(huì)弄混其中的一些模式蛉迹,為了加深理解,特此開設(shè)專題進(jìn)行總結(jié)放妈,主要是方便自己查閱北救,所以會(huì)比較簡練荐操,就不投稿了,無意中進(jìn)來的小伙伴們看不明白的話也請?jiān)彙?/p>


先簡單粗暴的羅列一下概念

1.單一職責(zé)原則描述的意思是每個(gè)類都只負(fù)責(zé)單一的功能珍策,切不可太多托启,并且一個(gè)類應(yīng)當(dāng)盡量的把一個(gè)功能做到極致

2.里氏替換原則這個(gè)原則表達(dá)的意思是一個(gè)子類應(yīng)該可以替換掉父類并且可以正常工作攘宙。(這個(gè)最難理解屯耸,需仔細(xì)體會(huì))

3.接口隔離原則也稱接口最小化原則,強(qiáng)調(diào)的是一個(gè)接口擁有的行為應(yīng)該盡可能的小蹭劈。

4.依賴倒置原則這個(gè)原則描述的是高層模塊不該依賴于低層模塊疗绣,二者都應(yīng)該依賴于抽象,抽象不應(yīng)該依賴于細(xì)節(jié)铺韧,細(xì)節(jié)應(yīng)該依賴于抽象多矮。

5.迪米特原則也稱最小知道原則,即一個(gè)類應(yīng)該盡量不要知道其他類太多的東西哈打,不要和陌生的類有太多接觸塔逃。

6.開-閉原則最后一個(gè)原則,一句話料仗,對修改關(guān)閉湾盗,對擴(kuò)展開放

下面說一下對各個(gè)原則的理解罢维。
單一職責(zé) 原則淹仑,從名字就可以看出來,就是使職責(zé)單一化肺孵,只負(fù)責(zé)干一件事情匀借,可以聯(lián)系到國家政府機(jī)構(gòu)的劃分

Paste_Image.png

大體分了如圖這些機(jī)構(gòu),每個(gè)機(jī)構(gòu)又劃分為很多個(gè)科室平窘,每個(gè)科室中的人也分管不同的工作吓肋,這樣設(shè)計(jì)的目的也是使職責(zé)單一化,提高運(yùn)作效率瑰艘。如果一個(gè)人負(fù)責(zé)的工作過多是鬼,不但增加了這個(gè)人的負(fù)擔(dān)而且也更容易出錯(cuò),想找這個(gè)人辦事兒的人太多紫新,也提高了復(fù)雜度和耦合均蜜。反觀到j(luò)ava的類中也是如此,所以單一職責(zé)原則是我覺得六大原則當(dāng)中最應(yīng)該遵守的原則芒率。

里氏替換原則簡單說來就是子類可以擴(kuò)展父類的功能囤耳,但不能改變父類原有的功能。
它包含以下4層含義:
1 子類可以實(shí)現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法充择。
2 子類中可以增加自己特有的方法德玫。
3 當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松椎麦。
4 當(dāng)子類的方法重載父類的方法時(shí)宰僧,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。

里氏替換原則的關(guān)鍵點(diǎn)在于不能覆蓋父類的非抽象方法观挎。父類中凡是已經(jīng)實(shí)現(xiàn)好的方法琴儿,實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類必須遵從這些規(guī)范键兜,但是如果子類對這些非抽象方法任意修改凤类,就會(huì)對整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義普气。
關(guān)于第3點(diǎn)做一下說明:
簡單理解:為什么是放大谜疤?因?yàn)楦割惙椒ǖ膮?shù)類型相對較小,所以當(dāng)傳入父類方法的參數(shù)類型现诀,重載的時(shí)候優(yōu)先匹配父類的方法夷磕,而子類的重載方法不會(huì)匹配,因此仍保證執(zhí)行父類的方法(子類繼承的時(shí)候其實(shí)操作的是子類中的父類成分)仔沿,所以業(yè)務(wù)邏輯不會(huì)改變坐桩。

關(guān)于第4點(diǎn)做一下說明:
簡單理解:如果是重載,由于前置條件的要求封锉,會(huì)調(diào)用到父類的函數(shù)绵跷,因此子函數(shù)不會(huì)被調(diào)用。
如果是覆蓋成福,則調(diào)用子類的函數(shù)碾局,這時(shí)子類的返回值比父類要求的小。因?yàn)楦割愓{(diào)用函數(shù)的時(shí)候奴艾,返回值的類型是父類的類型净当,而子類的返回值更小,賦值合法蕴潦。
Father F = ClassF.Func();//;用子類替換時(shí)Father F = ClassC.Func()是合法的 子類賦值父類轉(zhuǎn)是合法的像啼,父類賦值給子類是不合法的。

方法中的輸入?yún)?shù)稱為前置條件潭苞,這是什么意思呢忽冻?大家做過Web Service開發(fā)就應(yīng)該知道有一個(gè)“契約優(yōu)先”的原則,也就是先定義出WSDL接口此疹,制定好雙方的開發(fā)協(xié)議甚颂,然后再各自實(shí)現(xiàn)蜜猾。里氏替換原則也要求制定一個(gè)契約,就是父類或接口振诬,這種設(shè)計(jì)方法也叫做Design by Contract,契約設(shè)計(jì)衍菱,是與里氏替換原則融合在一起的赶么。契約制定了,也就同時(shí)制定了前置條件和后置條件脊串,前置條件就是你要讓我執(zhí)行辫呻,就必須滿足我的條件;后置條件就是我執(zhí)行完了需要反饋琼锋,標(biāo)準(zhǔn)是什么放闺。這個(gè)比較難理解,使用一個(gè)網(wǎng)友的例子

public class Father {
    public void func(HashMap m){
        System.out.println("執(zhí)行父類...");
    }
}

import java.util.Map;
public class Son extends Father{
    public void func(Map m){//方法的形參比父類的更寬松
        System.out.println("執(zhí)行子類...");
    }
}

import java.util.HashMap;
public class Client{
    public static void main(String[] args) {
        //引用基類的地方能透明地使用其子類的對象缕坎。
        //Father f = new Father();
        Son f = new Son();  
        HashMap h = new HashMap();
        f.func(h);
    }
}

輸出  :執(zhí)行父類...

ps:這里引申一個(gè)Java重載方法匹配優(yōu)先級問題
寫出以下程序的輸出:

public class Overload {
    
    public static void say(long arg) {
        System.out.println("hello long");
    }
    
    public static void say(Character arg) {
        System.out.println("hello character");
    }
    
    public static void say(char... arg) {
        System.out.println("hello char...");
    }
    
    // Serializable 參數(shù)
    public static void say(Serializable arg) {
        System.out.println("hello serializable");
    }
    
    
    public static void main(String[] args) {
        say('a');
    }

}

答案: hello long
這條題目考的是重載方法匹配的優(yōu)先級怖侦,那么它的匹配優(yōu)先級是怎樣的呢?
我們可以擴(kuò)充一下這個(gè)程序谜叹,加入一些其他的參數(shù)匾寝,然后測試一下:

public class Overload {

    // Object 參數(shù)
    public static void say(Object arg) {
        System.out.println("hello object");
    }
    
    
    // int 參數(shù)
    public static void say(int arg) {
        System.out.println("hello int");
    }
    
    // long 參數(shù) 
    public static void say(long arg) {
        System.out.println("hello long");
    }
    
    // char 參數(shù)
    public static void say(char arg) {
        System.out.println("hello char");
    }
    
    // Character 參數(shù)
    public static void say(Character arg) {
        System.out.println("hello character");
    }
    
    // 變長參數(shù)
    public static void say(char... arg) {
        System.out.println("hello char...");
    }
    
    // Serializable 參數(shù)
    public static void say(Serializable arg) {
        System.out.println("hello serializable");
    }
    
    
    public static void main(String[] args) {
               say('a');
    }

}

如果直接運(yùn)行的話,毫無疑問荷腊,輸出為: hello char
如果將char參數(shù)的函數(shù)注釋之后艳悔,會(huì)輸出什么呢?
答案是:hello int
因?yàn)檫@期間女仰,字符a發(fā)生了一次自動(dòng)轉(zhuǎn)型猜年,它除了能夠表示字符a外,還能表示數(shù)字65疾忍,于是重載方法匹配了int參數(shù)的重載方法乔外。
現(xiàn)在我們再將這個(gè)方法注釋了,輸出的結(jié)果大家應(yīng)該知道是什么了吧锭碳?
那就是:hello long
原因就是int自動(dòng)轉(zhuǎn)型為long袁稽。其實(shí)還可以轉(zhuǎn)化為float和double的,但不能轉(zhuǎn)化為byte和short擒抛,因?yàn)閏har到這兩個(gè)類型的轉(zhuǎn)化是不安全的推汽,這幾個(gè)類型的轉(zhuǎn)化優(yōu)先級為:char->int->long->float->double。
好歧沪,我們再繼續(xù)注釋掉這個(gè)函數(shù)歹撒,然后輸出是什么呢?
答案:hello character
為什么诊胞?大家應(yīng)該知道Java里面為每種基本數(shù)據(jù)類型都提供一種封裝類型吧暖夭?char對應(yīng)的就是Character锹杈,所以調(diào)用函數(shù)期間,當(dāng)找不到基本類型轉(zhuǎn)化的匹配之后迈着,char就會(huì)發(fā)生一次自動(dòng)裝箱竭望,變成了Character類型。
根本停不下來啊裕菠,再繼續(xù)注釋了它咬清,看下輸出。
輸出:hello serializable
這什么東西嘛奴潘。旧烧。。怎么會(huì)輸出這個(gè)家伙啊画髓。掘剪。。奈虾。原來是因?yàn)镃haracter實(shí)現(xiàn)了Serializable接口夺谁,當(dāng)它找不到匹配的類型之后,就會(huì)找它所實(shí)現(xiàn)的接口愚墓。但是予权,如果我們再增加一個(gè)重載函數(shù):

public static void say(Comparable arg) { 
    System.out.println("hello Comparable");
}

那么就會(huì)報(bào)錯(cuò)了, 因?yàn)镃haracter實(shí)現(xiàn)了Serializable和Comparable這兩個(gè)接口浪册,而接口匹配的優(yōu)先級是一樣的扫腺,編譯器無法判斷轉(zhuǎn)型為哪種類型,提示類型模糊村象,拒絕編譯笆环。
好,繼續(xù)注釋掉Serializable參數(shù)的函數(shù)厚者,看輸出:hello object
接口找不到匹配之后躁劣,就會(huì)開始找匹配的父類,優(yōu)先級是順著繼承鏈库菲,由下往上進(jìn)行匹配账忘。
最后,連這個(gè)函數(shù)也注釋了的話熙宇,大家應(yīng)該知道輸出的是什么了吧鳖擒?
當(dāng)然就是:hello char...
由此可見,變長參數(shù)的優(yōu)先級是最低的烫止。


接口隔離原則 講一個(gè)極端點(diǎn)的例子蒋荚,平常很多人常說,原來上學(xué)的時(shí)候?qū)W的那些個(gè)東西有啥用馆蠕,生活中也用不到期升,先不說這種觀點(diǎn)正確與否惊奇,我們以這種想法去想象一下,如果把人所具有的技能定義為一個(gè)接口播赁,以上的觀點(diǎn)就是往這個(gè)接口中定義了 語文 英語 數(shù)學(xué) 物理 化學(xué) 等等方法颂郎,有的人可能一輩子沒學(xué)過英語,那么這個(gè)方法對于這些人來說就是個(gè)不必有實(shí)現(xiàn)的方法行拢,但接口這偏偏定義了祖秒,那可能就會(huì)屈就的實(shí)現(xiàn)成一個(gè)空方法,這還沒什么舟奠,最嚴(yán)重的是會(huì)給使用者造成假象,即這個(gè)實(shí)現(xiàn)類擁有接口中所有的行為房维,結(jié)果調(diào)用方法時(shí)卻沒收獲到想要的結(jié)果沼瘫。你和他說hello,他也不會(huì)跟你回答 world!
所以定義接口的時(shí)候咙俩,其中的方法要是實(shí)現(xiàn)這個(gè)接口的類所共有的才好耿戚。


依賴倒置原則 相對于細(xì)節(jié)的多變性,抽象的東西要穩(wěn)定的多阿趁。以抽象為基礎(chǔ)搭建起來的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來的架構(gòu)要穩(wěn)定的多膜蛔。在java中,抽象指的是接口或者抽象類脖阵,細(xì)節(jié)就是具體的實(shí)現(xiàn)類皂股,使用接口或者抽象類的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作命黔,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類去完成呜呐。

class Newspaper implements IReader {
 public String getContent(){
 return "林書豪17+9助尼克斯擊敗老鷹……";
 }
}
class Book implements IReader{
 public String getContent(){
 return "很久很久以前有一個(gè)阿拉伯的故事……";
 }
}
 
class Mother{
 public void narrate(IReader reader){
 System.out.println("媽媽開始講故事");
 System.out.println(reader.getContent());
 }
}
 
public class Client{
 public static void main(String[] args){
 Mother mother = new Mother();
 mother.narrate(new Book());
 mother.narrate(new Newspaper());
 }
}

迪米特原則應(yīng)該將細(xì)節(jié)全部高內(nèi)聚于類的內(nèi)部,其他的類只需要知道這個(gè)類主要提供的功能即可悍募。 所謂高內(nèi)聚就是盡可能將一個(gè)類的細(xì)節(jié)全部寫在這個(gè)類的內(nèi)部蘑辑,不要漏出來給其他類知道,否則其他類就很容易會(huì)依賴于這些細(xì)節(jié)坠宴,這樣類之間的耦合度就會(huì)急速上升洋魂,這樣做的后果往往是一個(gè)類隨便改點(diǎn)東西,依賴于它的類全部都要改喜鼓。
迪米特原則雖說是指的一個(gè)類應(yīng)當(dāng)盡量不要知道其他類太多細(xì)節(jié)副砍,但其實(shí)更重要的是一個(gè)類應(yīng)當(dāng)不要讓外部的類知道自己太多。兩者是相輔相成的颠通,只要你將類的封裝性做的很好址晕,那么外部的類就無法依賴當(dāng)中的細(xì)節(jié)。

開閉原則 這個(gè)原則更像是前五個(gè)原則的總綱顿锰,前五個(gè)原則就是圍著它轉(zhuǎn)的谨垃,只要我們盡量的遵守前五個(gè)原則启搂,那么設(shè)計(jì)出來的系統(tǒng)應(yīng)該就比較符合開閉原則了,相反刘陶,如果你違背了太多胳赌,那么你的系統(tǒng)或許也不太遵循開閉原則。

參考:
設(shè)計(jì)模式詳解(總綱)
里氏替換原則
第3章 里氏替換原則(LSP)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匙隔,一起剝皮案震驚了整個(gè)濱河市疑苫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纷责,老刑警劉巖捍掺,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異再膳,居然都是意外死亡挺勿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門喂柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來不瓶,“玉大人,你說我怎么就攤上這事灾杰∥秘ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵艳吠,是天一觀的道長麦备。 經(jīng)常有香客問我,道長讲竿,這世上最難降的妖魔是什么泥兰? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮题禀,結(jié)果婚禮上鞋诗,老公的妹妹穿的比我還像新娘。我一直安慰自己迈嘹,他們只是感情好削彬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秀仲,像睡著了一般融痛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上神僵,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天雁刷,我揣著相機(jī)與錄音霎匈,去河邊找鬼寨闹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亮曹。 我是一名探鬼主播宵距,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼然低,長吁一口氣:“原來是場噩夢啊……” “哼英遭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起企蹭,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤白筹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后谅摄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徒河,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年送漠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虚青。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡螺男,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纵穿,到底是詐尸還是另有隱情下隧,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布谓媒,位于F島的核電站淆院,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏句惯。R本人自食惡果不足惜土辩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抢野。 院中可真熱鬧拷淘,春花似錦、人聲如沸指孤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恃轩。三九已至结洼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叉跛,已是汗流浹背松忍。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筷厘,地道東北人鸣峭。 一個(gè)月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓宏所,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叽掘。 傳聞我的和親對象是個(gè)殘疾皇子楣铁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容