軟件設(shè)計(jì)的六大原則剖析

我們平時(shí)編寫代碼時(shí)蝶桶,很少有人為了刻意迎合軟件設(shè)計(jì)原則而編寫。其實(shí)掉冶,有時(shí)候是你用到了其中的某個(gè)或多個(gè)設(shè)計(jì)原則真竖,而不自知而已儡蔓。也有可能是有的人壓根就不知道設(shè)計(jì)原則是什么。

不過疼邀,沒關(guān)系喂江,為了搞明白既抽象又玄幻的六大準(zhǔn)則,我總結(jié)了一句話來概括每一種設(shè)計(jì)原則所體現(xiàn)的主要思想旁振。

里氏替換原則是指繼承時(shí)不要破壞父類原有的功能获询;依賴倒置原則是指要面向接口編程;開閉原則是指對(duì)擴(kuò)展是開放的拐袜,對(duì)修改是關(guān)閉的吉嚣;職責(zé)單一原則是指實(shí)現(xiàn)類的職責(zé)要單一;接口隔離原則是指設(shè)計(jì)的接口要盡量簡(jiǎn)單蹬铺,專一尝哆;迪米特法則是指要降低類之間的耦合度。

下面一一介紹六大類設(shè)計(jì)原則甜攀,看完之后秋泄,你會(huì)對(duì)上邊的總結(jié)有更深的理解。

一规阀、里氏替換原則

里氏替換原則恒序,乍一看名字,讓人摸不著頭腦谁撼。其實(shí)歧胁,這是一位姓里的女士提出來的,因此用她的姓氏命名厉碟。里氏替換原則喊巍,通俗來講,就是指子類繼承父類時(shí)箍鼓,可以擴(kuò)展父類的功能崭参,但是不要修改父類原有的功能。什么意思呢袄秩,舉個(gè)例子阵翎。

//父類
public class Calculate {
    public int cal(int a,int b){
        return a + b;
    }
}
//子類
public class Calculate2 extends Calculate {
    public int cal(int a,int b){
        return a - b;
    }
}
//測(cè)試
public class TestCal {
    public static void main(String[] args) {
        Calculate2 cal2 = new Calculate2();
        int res = cal2.cal(1, 1);
        System.out.println("1+1="+res); // 1+1 = 0
    }
}

子類繼承了父類之后逢并,想實(shí)現(xiàn)新功能之剧,卻沒有擴(kuò)展新方法,而是重寫了父類的cal方法砍聊,因此導(dǎo)致結(jié)果 1+1=0. 這就違反了里氏替換原則背稼。

應(yīng)該把子類Calculate2修改為,添加一個(gè)新方法cal2來實(shí)現(xiàn)相減功能

public class Calculate2 extends Calculate {
    public int cal2(int a, int b){
        return a - b;
    }
}

public class TestCal {
    public static void main(String[] args) {
        Calculate2 cal2 = new Calculate2();
        int res = cal2.cal2(1, 1);
        System.out.println("1-1="+res); // 1-1=0
    }
}

有心的人可能會(huì)發(fā)現(xiàn)玻蝌,里氏替換原則規(guī)定子類不能重寫父類的方法蟹肘。這不是和面向?qū)ο笾械娜筇卣髦弧岸鄳B(tài)”沖突嗎词疼,多態(tài)實(shí)現(xiàn)的一個(gè)重要前提就是子類繼承父類并重寫父類的方法啊。

其實(shí)帘腹,剛開始學(xué)習(xí)里氏替換原則贰盗,我也產(chǎn)生了這樣的疑惑。后來查了很多資料阳欲,才明白舵盈,子類不應(yīng)該去重寫父類已經(jīng)實(shí)現(xiàn)的方法(非抽象方法),而是去實(shí)現(xiàn)父類的抽象方法球化。也就是說秽晚,盡量要基于抽象類和接口的繼承,而不是基于可實(shí)例化的父類繼承筒愚。關(guān)于這一點(diǎn)的解釋赴蝇,可以看這篇文章,我感覺總結(jié)的挺不錯(cuò)的:http://www.reibang.com/p/e6a7bbde8844?utm_campaign

二巢掺、單一職責(zé)原則

簡(jiǎn)單來說句伶,就是要控制類的粒度大小,降低類的復(fù)雜度陆淀,一個(gè)類只負(fù)責(zé)一項(xiàng)職責(zé)熄阻。

例如,在研發(fā)一個(gè)產(chǎn)品新功能時(shí)倔约。需要項(xiàng)目經(jīng)理接需求秃殉,評(píng)估工作量,然后分發(fā)任務(wù)給程序員浸剩。程序員钾军,根據(jù)需求編寫代碼,然后自測(cè)绢要。各司其職吏恭,才能保證項(xiàng)目穩(wěn)定向前推進(jìn)。其類圖如下


file

另外重罪,單一職責(zé)原則也適用于方法樱哼,一個(gè)方法只做一件事。

三剿配、依賴倒置原則

依賴倒置原則的定義為:高層模塊不應(yīng)該依賴低層模塊搅幅,二者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié)呼胚,細(xì)節(jié)應(yīng)該依賴抽象茄唐。其實(shí),就是在說要面向抽象蝇更,面向接口編程沪编。

舉個(gè)栗子呼盆,如果一個(gè)學(xué)生去學(xué)習(xí)歷史知識(shí),只需要把歷史書給他就可以了

public class History {
    public String getKnowledge(){
        return "歷史知識(shí)";
    }
}

public class Student {
    public void study(History history){
        System.out.println("學(xué)習(xí)" + history.getKnowledge());
    }
}

public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.study(new History()); //學(xué)習(xí)歷史知識(shí)
    }
}

但是蚁廓,如果他需要學(xué)習(xí)地理知識(shí)呢访圃,我們需要把History改為Geography,然后修改study方法的參數(shù)類型為Geography

public class Geography {
    public String getKnowledge(){
        return "地理知識(shí)";
    }
}

public class Student {
    public void study(Geography geography){
        System.out.println("學(xué)習(xí)" + geography.getKnowledge()); 
    }
}
//學(xué)習(xí)地理知識(shí)

雖然相嵌,這樣實(shí)現(xiàn)也是可以的挽荠,但是通用性太差,類之間的耦合度太高了平绩。設(shè)想圈匆,如果該學(xué)生又要學(xué)習(xí)數(shù)學(xué)知識(shí)呢,語(yǔ)文呢捏雌,英語(yǔ)呢跃赚,是不是每次都要修改study方法。這樣的設(shè)計(jì)不符合依賴倒置原則性湿,應(yīng)該把各個(gè)學(xué)科知識(shí)抽象出來纬傲,定義一個(gè)接口IKnowledge,然后每個(gè)學(xué)科去實(shí)現(xiàn)這個(gè)接口肤频,而study方法的參數(shù)傳一個(gè)固定類型IKnowledge就可以了叹括。

public interface IKnowledge {
    String getKnowledge();
}

public class History implements IKnowledge{
    public String getKnowledge(){
        return "歷史知識(shí)";
    }
}

public class Geography implements IKnowledge{
    public String getKnowledge(){
        return "地理知識(shí)";
    }
}

public class Student {
    public void study(IKnowledge iKnowledge){
        System.out.println("學(xué)習(xí)" + iKnowledge.getKnowledge());
    }
}

public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.study(new History());  //學(xué)習(xí)歷史知識(shí)
        stu.study(new Geography());  //學(xué)習(xí)地理知識(shí)
    }
}

這樣的話,如果需要再學(xué)習(xí)英語(yǔ)知識(shí)宵荒,只需要定義一個(gè)English類汁雷,去實(shí)現(xiàn)IKnowledge接口就可以了。 這就是依賴倒置原則的面向接口編程报咳。

它們之間的類圖關(guān)系如下


file

四侠讯、接口隔離原則

接口隔離原則的定義:客戶端不應(yīng)該依賴它不需要的接口;一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上暑刃。

什么意思呢厢漩,就是說設(shè)計(jì)接口的時(shí)候,不要把一大堆需要實(shí)現(xiàn)的抽象方法都定義到同一個(gè)接口中岩臣,應(yīng)該根據(jù)不同的功能溜嗜,來拆分成不同的接口。我們知道架谎,實(shí)現(xiàn)類去實(shí)現(xiàn)接口的時(shí)候炸宵,需要實(shí)現(xiàn)所有的抽象方法。如果接口中有某些不需要的方法狐树,也需要實(shí)現(xiàn)焙压,但是方法體卻是空的,這樣完全沒有意義抑钟。

例如涯曲,我定義一個(gè)Animal的接口,用獅子去實(shí)現(xiàn)接口

public interface Animal {
    void eat();
    void fly();
    void run();
}

public class Lion implements Animal {
    @Override
    public void eat() {
        System.out.println("獅子吃肉");
    }

    @Override
    public void fly() {

    }

    @Override
    public void run() {
        System.out.println("獅子奔跑");
    }
}

很明顯在塔,獅子是不會(huì)飛的幻件,fly方法的方法體是空的。這樣設(shè)計(jì)蛔溃,不符合接口隔離原則绰沥。因此,我們把接口進(jìn)行拆分贺待,拆分為Animal徽曲,IFly,IRun三個(gè)接口麸塞,讓獅子選擇性實(shí)現(xiàn)秃臣。

public interface Animal {
    void eat();
}

public interface IFly {
    void fly();
}

public interface IRun {
    void run();
}

public class Lion implements Animal,IRun {
    @Override
    public void eat() {
        System.out.println("獅子吃肉");
    }

    @Override
    public void run() {
        System.out.println("獅子奔跑");
    }
}
// 獅子只需要實(shí)現(xiàn)吃的方法和奔跑的方法就可以了,不需要實(shí)現(xiàn)IFly接口哪工。

可以發(fā)現(xiàn)奥此,接口隔離原則和職責(zé)單一原則非常之相似,但其實(shí)是不同的雁比。職責(zé)單一原則主要是約束類稚虎,針對(duì)的是具體的實(shí)現(xiàn),強(qiáng)調(diào)類職責(zé)的單一偎捎。而接口隔離原則主要是約束接口的蠢终,注重的是高層的抽象和對(duì)接口依賴的隔離。

另外茴她,需要注意蜕径,接口設(shè)計(jì)的過細(xì)也不太好,會(huì)增大系統(tǒng)的復(fù)雜度败京。想象一下兜喻,你為了實(shí)現(xiàn)某些功能,卻需要實(shí)現(xiàn)十幾個(gè)接口的場(chǎng)景是多崩潰吧赡麦。因此需要適度地進(jìn)行接口拆分朴皆。

五、迪米特法則

迪米特法則定義:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解泛粹。什么意思呢遂铡,就是說要盡量降低類之間的耦合度,提高類的獨(dú)立性晶姊,這樣當(dāng)一個(gè)類修改的時(shí)候扒接,對(duì)其他類的影響也會(huì)降到最低。

通俗點(diǎn)講,就是一個(gè)類對(duì)它依賴的類知道的越少越好钾怔。對(duì)于被依賴的類來說碱呼,不管內(nèi)部實(shí)現(xiàn)多復(fù)雜,只需給其他類暴露一個(gè)可以調(diào)用的公共方法宗侦。

舉個(gè)簡(jiǎn)單的例子愚臀。當(dāng)公司老板需要下發(fā)一個(gè)任務(wù)時(shí),不會(huì)直接把每個(gè)員工都叫到一起矾利,給每個(gè)人分配具體的任務(wù)姑裂。而是先召集各部門經(jīng)理給他們發(fā)布任務(wù),然后部門經(jīng)理再給下邊員工分派任務(wù)男旗。老板只需要監(jiān)督部門經(jīng)理即可舶斧,不需要關(guān)心部門經(jīng)理給每個(gè)員工分配的任務(wù)具體是什么。

用代碼可以這樣表示

public class Employee {
    public void doTask(){
        System.out.println("員工執(zhí)行任務(wù)");
    }
}

public class DeptManager {
    public void task(){
        System.out.println("部門領(lǐng)導(dǎo)發(fā)布任務(wù)");
        Employee employee = new Employee();
        employee.doTask();
    }
}

public class Boss {
    private DeptManager deptMgr;

    public void setDeptMgr(DeptManager mgr){
        this.deptMgr = mgr;
    }

    public void task(){
        System.out.println("老板發(fā)布任務(wù)");
        deptMgr.task();
    }
}

public class TestD {
    public static void main(String[] args) {
        Boss boss = new Boss();
        boss.setDeptMgr(new DeptManager());
        boss.task();
    }
}
//老板發(fā)布任務(wù)
//部門領(lǐng)導(dǎo)發(fā)布任務(wù)
//員工執(zhí)行任務(wù)

這樣察皇,老板跟具體的每個(gè)員工就沒有任何直接聯(lián)系茴厉,降低了耦合度。

可以看到让网,其實(shí)部門經(jīng)理在這其中充當(dāng)了中介的作用呀忧,用于建立老板和員工之間的聯(lián)系。需要注意溃睹,要適度的使用中介而账,如果中介太多,就會(huì)導(dǎo)致系統(tǒng)復(fù)雜度太高因篇,通訊的效率降低泞辐。就如同一個(gè)公司,部門越多竞滓,級(jí)別層級(jí)越多咐吼,越不容易管理,溝通成本增加商佑,執(zhí)行任務(wù)的效率下降锯茄。因此,需要合理設(shè)計(jì)中介類茶没。

六肌幽、開閉原則

開閉原則定義:對(duì)擴(kuò)展是開放的,對(duì)修改是關(guān)閉的抓半。

其實(shí)喂急,這句話就體現(xiàn)了封裝,繼承和多態(tài)的思想笛求。一個(gè)實(shí)體類廊移,如果已經(jīng)實(shí)現(xiàn)了原有的功能糕簿,就不應(yīng)該再對(duì)其進(jìn)行修改,需要的話應(yīng)該對(duì)其進(jìn)行功能擴(kuò)展狡孔。這句話聽起來是不是跟里氏替換原則特別像懂诗。其實(shí),開閉原則更像是對(duì)其他幾個(gè)原則的總結(jié)步氏,最終要達(dá)到的目的就是用抽象構(gòu)建高層模塊响禽,用實(shí)現(xiàn)擴(kuò)展具體的細(xì)節(jié)徒爹。

里氏替換原則和依賴倒置原則告訴你應(yīng)該對(duì)類和方法進(jìn)行抽象荚醒。單一職責(zé)和接口隔離告訴你應(yīng)該怎樣做抽象才合理,迪米特法則告訴你具體實(shí)現(xiàn)怎樣做才能做到高內(nèi)聚隆嗅,低耦合界阁。

其實(shí),六大設(shè)計(jì)原則就規(guī)定了一些規(guī)則胖喳,它告訴你按照這樣做更好泡躯,但是如果你非要不遵守規(guī)則,也不是不行丽焊,代碼照樣可以跑较剃,只不過是增加了代碼出問題的概率,健壯性也不好技健,可維護(hù)性不高写穴。這就像我們生活中的很多規(guī)則,如過馬路雌贱,需要看紅綠燈啊送。但是,你非要不看欣孤,硬闖紅燈馋没,也沒人能把你怎樣,不過是增加了你被撞的概率而已降传。所以篷朵,遵守規(guī)則,能最大限度降低我們的損失婆排。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末声旺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泽论,更是在濱河造成了極大的恐慌艾少,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翼悴,死亡現(xiàn)場(chǎng)離奇詭異缚够,居然都是意外死亡幔妨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門谍椅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來误堡,“玉大人,你說我怎么就攤上這事雏吭∷” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵杖们,是天一觀的道長(zhǎng)悉抵。 經(jīng)常有香客問我,道長(zhǎng)摘完,這世上最難降的妖魔是什么姥饰? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮孝治,結(jié)果婚禮上列粪,老公的妹妹穿的比我還像新娘。我一直安慰自己谈飒,他們只是感情好岂座,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杭措,像睡著了一般费什。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓤介,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天吕喘,我揣著相機(jī)與錄音,去河邊找鬼刑桑。 笑死氯质,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祠斧。 我是一名探鬼主播闻察,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼琢锋!你這毒婦竟也來了辕漂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吴超,失蹤者是張志新(化名)和其女友劉穎钉嘹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲸阻,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跋涣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年缨睡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陈辱。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奖年,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沛贪,到底是詐尸還是另有隱情陋守,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布利赋,位于F島的核電站水评,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏隐砸。R本人自食惡果不足惜之碗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一蝙眶、第九天 我趴在偏房一處隱蔽的房頂上張望季希。 院中可真熱鬧,春花似錦幽纷、人聲如沸式塌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峰尝。三九已至,卻和暖如春收恢,著一層夾襖步出監(jiān)牢的瞬間武学,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工伦意, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留火窒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓驮肉,卻偏偏與公主長(zhǎng)得像熏矿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子离钝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • 單一職責(zé)原則 (SRP) 全稱 SRP , Single Responsibility Principle 單一職...
    米莉_L閱讀 1,765評(píng)論 2 5
  • 前言 關(guān)于設(shè)計(jì)模式六大設(shè)計(jì)原則的資料網(wǎng)上很多票编,但感覺很多地方解釋地都太過于籠統(tǒng)化,特此再總結(jié)一波卵渴。 優(yōu)化第一步-單...
    ghroost閱讀 1,104評(píng)論 0 5
  • 設(shè)計(jì)模式六大原則 設(shè)計(jì)模式六大原則(1):?jiǎn)我宦氊?zé)原則 定義:不要存在多于一個(gè)導(dǎo)致類變更的原因慧域。通俗的說,即一個(gè)類...
    viva158閱讀 766評(píng)論 0 1
  • 目標(biāo):預(yù)祝明天鄭伯伯他們的事情順利進(jìn)行浪读。 種下的好種子: 1.給三位姐姐買了巧克力昔榴,并且分享給他們宛裕,讓她們感受一下...
    Betty麗麗閱讀 131評(píng)論 0 1
  • 我不知道我為什么變成了一個(gè)這樣的人,徘徊與邊緣的人论泛,當(dāng)一群人在一起熱鬧的高興的溫馨的嬉鬧交談時(shí)揩尸,我一個(gè)安靜的坐著,...
    sid_9ff8閱讀 143評(píng)論 0 0