設(shè)計(jì)模式之開(kāi)篇

本文集網(wǎng)絡(luò)上文章及自己coding和理解的結(jié)果而來(lái)栈暇,是設(shè)計(jì)模式學(xué)習(xí)的開(kāi)篇宁脊。

本文介紹設(shè)計(jì)模式的一些概念,分類(lèi)撕彤,和設(shè)計(jì)原則鱼鸠。

概念:

設(shè)計(jì)模式(Design Patterns)是一套被反復(fù)使用、多數(shù)人知曉的羹铅、經(jīng)過(guò)分類(lèi)的蚀狰、代碼設(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)在中都有相應(yīng)的原理來(lái)與之對(duì)應(yīng),每一個(gè)模式描述了一個(gè)在我們周?chē)粩嘀貜?fù)發(fā)生的問(wèn)題深夯,以及該問(wèn)題的核心解決方案抖格,這也是它能被廣泛應(yīng)用的原因。

分類(lèi):

總共23種設(shè)計(jì)模式咕晋,分為三類(lèi):

  • 創(chuàng)建型模式雹拄,共五種:工廠方法模式、抽象工廠模式掌呜、單例模式滓玖、建造者模式、原型模式质蕉。

  • 結(jié)構(gòu)型模式势篡,共七種:適配器模式、裝飾器模式模暗、代理模式禁悠、外觀模式、橋接模式兑宇、組合模式碍侦、享元模式。

  • 行為型模式隶糕,共十一種:策略模式瓷产、模板方法模式、觀察者模式枚驻、迭代子模式濒旦、責(zé)任鏈模式、命令模式再登、備忘錄模式尔邓、狀態(tài)模式、訪(fǎng)問(wèn)者模式霎冯、中介者模式铃拇、解釋器模式。

設(shè)計(jì)原則:

  1. 開(kāi)閉原則(Open Close Principle)

開(kāi)閉原則就是說(shuō)對(duì)擴(kuò)展開(kāi)放沈撞,對(duì)修改關(guān)閉慷荔。在程序需要進(jìn)行拓展的時(shí)候,不能去修改原有的代碼,實(shí)現(xiàn)一個(gè)熱插拔的效果显晶。所以一句話(huà)概括就是:為了使程序的擴(kuò)展性好贷岸,易于維護(hù)和升級(jí)。想要達(dá)到這樣的效果磷雇,我們需要使用接口和抽象類(lèi)偿警,后面的具體設(shè)計(jì)中我們會(huì)提到這點(diǎn)。
下面我們以簡(jiǎn)單工廠和工廠方法為例唯笙,體會(huì)下開(kāi)閉原則:

  • 簡(jiǎn)單工廠
/**
 * 功能簡(jiǎn)述: 運(yùn)算符抽象類(lèi)螟蒸,輸入兩個(gè)整數(shù),返回結(jié)果
 *
 * @author 
 */
public abstract class Operation {

    protected int numA;

    protected int numB;

    public abstract int getResult();
}
/**
 * 功能簡(jiǎn)述: 加法實(shí)現(xiàn)類(lèi)
 *
 * @author
 */
public class OperationAdd extends Operation {

    @Override
    public int getResult() {
        return numA + numB;
    }

}
/**
 * 功能簡(jiǎn)述: 減法實(shí)現(xiàn)類(lèi)
 *
 * @author
 */
public class OperationSub extends Operation {

    @Override
    public int getResult() {
        return numA - numB;
    }

}

/**
 * 功能簡(jiǎn)述: 簡(jiǎn)單工廠類(lèi)
 *
 * @author
 */
public class OperationFactory {
    public static Operation createOperate(String operate) {
        Operation oper = null;
        switch (operate) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
        }
        return oper;
    }
}
/**
 * 功能簡(jiǎn)述: 客戶(hù)端調(diào)用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        //輸出3
        Operation oper = OperationFactory.createOperate("+");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //輸出-1
        oper = OperationFactory.createOperate("-");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
    }

}

上述的代碼是簡(jiǎn)單工廠的一個(gè)例子崩掘,比較簡(jiǎn)單七嫌。下面我們需要新增一個(gè)乘法的實(shí)現(xiàn)類(lèi),我們需要怎么做苞慢?首先诵原,需要寫(xiě)一個(gè)OperationMul繼承Operation,實(shí)現(xiàn)父類(lèi)中的getResult方法挽放,返回乘積绍赛;其次,需要在工廠類(lèi)OperationFactory中新增一段case語(yǔ)句辑畦,當(dāng)case"*"號(hào)時(shí)返回OperationMul實(shí)現(xiàn)吗蚌;最后,在client方法中測(cè)試實(shí)現(xiàn)纯出。

/**
 * 功能簡(jiǎn)述: 乘法實(shí)現(xiàn)類(lèi)
 *
 * @author 
 */
public class OperationMul extends Operation {

    @Override
    public int getResult() {
        return numA * numB;
    }

}
/**
 * 功能簡(jiǎn)述: 簡(jiǎn)單工廠類(lèi)褪测,改造,新增了對(duì)*的處理
 *
 * @author
 */
public class OperationFactory {
    public static Operation createOperate(String operate) {
        Operation oper = null;
        switch (operate) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            case "*":
                oper = new OperationMul();
                break;
        }
        return oper;
    }
}
/**
 * 功能簡(jiǎn)述: 客戶(hù)端調(diào)用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        //輸出3
        Operation oper = OperationFactory.createOperate("+");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //輸出-1
        oper = OperationFactory.createOperate("-");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //輸出2
        oper = OperationFactory.createOperate("*");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
    }

}

我們分析下上面的代碼潦刃,為了新增對(duì)"*"的處理,我們新增了一個(gè)類(lèi)懈叹,同時(shí)修改了一個(gè)類(lèi)乖杠,在這個(gè)例子中我們對(duì)新增開(kāi)放,同時(shí)也對(duì)修改開(kāi)放了澄成。因?yàn)樾薷牧撕?jiǎn)單工廠類(lèi)胧洒,雖然加的代碼不多,但為了驗(yàn)證代碼的正確性墨状,我們不得不回歸測(cè)試"+","-"的使用場(chǎng)景卫漫。

  • 工廠方法
    工廠方法是工廠模式的變體,遵循了開(kāi)閉原則肾砂,下面看代碼列赎。我們稍微簡(jiǎn)化下簡(jiǎn)單工廠的實(shí)現(xiàn)方法,一開(kāi)始我們只有一個(gè)"+"實(shí)現(xiàn)類(lèi)镐确,之后我們?cè)傩略?-"實(shí)現(xiàn)類(lèi)包吝。
/**
 * 功能簡(jiǎn)述: 運(yùn)算符抽象類(lèi)饼煞,輸入兩個(gè)整數(shù),返回結(jié)果
 *
 * @author 
 */
public abstract class Operation {

    protected int numA;

    protected int numB;

    public abstract int getResult();
}
/**
 * 功能簡(jiǎn)述: 加法實(shí)現(xiàn)類(lèi)
 *
 * @author
 */
public class OperationAdd extends Operation {

    @Override
    public int getResult() {
        return numA + numB;
    }

}
/**
 * 功能簡(jiǎn)述: 抽象工廠類(lèi)诗越,此類(lèi)只定義接口砖瞧,不定義實(shí)現(xiàn),此為和簡(jiǎn)單工廠的區(qū)別之一
 *
 * @author
 */
public interface OperationFactory {

    Operation createOperate();
}
/**
 * 功能簡(jiǎn)述: 加法工廠類(lèi)
 *
 * @author
 */
public class OperationAddFactory implements OperationFactory {

    @Override
    public Operation createOperate() {
        return new OperationAdd();
    }
}
/**
 * 功能簡(jiǎn)述: 客戶(hù)端調(diào)用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        OperationAddFactory addFactory = new OperationAddFactory();
        Operation oper = addFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //輸出3
        System.out.println(oper.getResult());
    }
}

下面同樣嚷狞,我們來(lái)新增一個(gè)減法實(shí)現(xiàn)類(lèi)块促。

/**
 * 功能簡(jiǎn)述: 減法實(shí)現(xiàn)類(lèi)
 *
 * @author 
 */
public class OperationSub extends Operation {

    @Override
    public int getResult() {
        return numA - numB;
    }
}
/**
 * 功能簡(jiǎn)述:減法工廠類(lèi)
 *
 * @author
 */
public class OperationSubFactory implements OperationFactory {

    @Override
    public Operation createOperate() {
        return new OperationSub();
    }
}
/**
 * 功能簡(jiǎn)述: 測(cè)試實(shí)現(xiàn)
 *
 * @author
 */
public class Client {

    /**
     * 功能描述: <br>
     *
     * @param args
     */
    public static void main(String[] args) {
        OperationAddFactory addFactory = new OperationAddFactory();
        Operation oper = addFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //輸出3
        System.out.println(oper.getResult());

        OperationSubFactory subFactory = new OperationSubFactory();
        oper = subFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //輸出-1
        System.out.println(oper.getResult());
    }
}

同樣,我們分析下工廠方法模式床未,我們新增了一個(gè)減法實(shí)現(xiàn)類(lèi)竭翠,及減法工廠類(lèi),但并沒(méi)有修改加法相關(guān)的任何代碼即硼,真正做到了開(kāi)閉原則逃片。至于工廠類(lèi)的區(qū)別,那正是簡(jiǎn)單工廠和工廠方法的區(qū)別之一只酥,在工廠方法這種實(shí)現(xiàn)中褥实,不僅抽象了操作,還抽象了工廠裂允。
讀到這里损离,大家應(yīng)該對(duì)開(kāi)閉原則有個(gè)初步的印象了。

  1. 里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向?qū)ο笤O(shè)計(jì)的基本原則之一绝编。
里氏代換原則中說(shuō)僻澎,任何基類(lèi)可以出現(xiàn)的地方,子類(lèi)一定可以出現(xiàn)十饥。
LSP是繼承復(fù)用的基石窟勃,只有當(dāng)衍生類(lèi)可以替換掉基類(lèi),軟件單位的功能不受到影響時(shí)逗堵,基類(lèi)才能真正被復(fù)用秉氧,而衍生類(lèi)也能夠在基類(lèi)的基礎(chǔ)上增加新的行為。里氏代換原則是對(duì)“開(kāi)-閉”原則的補(bǔ)充蜒秤。實(shí)現(xiàn)“開(kāi)-閉”原則的關(guān)鍵步驟就是抽象化汁咏。而基類(lèi)與子類(lèi)的繼承關(guān)系就是抽象化的具體實(shí)現(xiàn),所以里氏代換原則是對(duì)實(shí)現(xiàn)抽象化的具體步驟的規(guī)范作媚。

里氏替換原則包含以下4層含義:

  • 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法攘滩,但是不能覆蓋父類(lèi)的非抽象方法。
  • 子類(lèi)中可以增加自己特有的方法纸泡。
  • 當(dāng)子類(lèi)覆蓋或?qū)崿F(xiàn)父類(lèi)的方法時(shí)漂问,方法的前置條件(即方法的形參)要比父類(lèi)方法的輸入?yún)?shù)更寬松。
  • 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類(lèi)更嚴(yán)格级解。

下面逐個(gè)闡述下:

  • 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法冒黑,但是不能覆蓋父類(lèi)的非抽象方法。

在我們做系統(tǒng)設(shè)計(jì)時(shí)勤哗,經(jīng)常會(huì)設(shè)計(jì)接口或抽象類(lèi)抡爹,然后由子類(lèi)來(lái)實(shí)現(xiàn)抽象方法,這里使用的其實(shí)就是里氏替換原則芒划。子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法很好理解冬竟,事實(shí)上,子類(lèi)也必須完全實(shí)現(xiàn)父類(lèi)的抽象方法民逼,哪怕寫(xiě)一個(gè)空方法泵殴,否則會(huì)編譯報(bào)錯(cuò)。
里氏替換原則的關(guān)鍵點(diǎn)在于不能覆蓋父類(lèi)的非抽象方法拼苍。父類(lèi)中凡是已經(jīng)實(shí)現(xiàn)好的方法笑诅,實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類(lèi)必須遵從這些規(guī)范疮鲫,但是如果子類(lèi)對(duì)這些非抽象方法任意修改吆你,就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義俊犯。
看一個(gè)例子:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}
 
public class C1 extends C{
    @Override
    public int func(int a, int b) {
        return a-b;
    }
}
 
public class Client{
    public static void main(String[] args) {
        C c = new C1();
        //運(yùn)行結(jié)果:2+1=1
        System.out.println("2+1=" + c.func(2, 1));
    }
}

上面的運(yùn)行結(jié)果明顯是錯(cuò)誤的妇多。類(lèi)C1繼承C,后來(lái)需要增加新功能燕侠,類(lèi)C1并沒(méi)有新寫(xiě)一個(gè)方法者祖,而是直接重寫(xiě)了父類(lèi)C的func方法,違背里氏替換原則绢彤,引用父類(lèi)的地方并不能透明的使用子類(lèi)的對(duì)象七问,導(dǎo)致運(yùn)行結(jié)果出錯(cuò)。

  • 子類(lèi)中可以增加自己特有的方法茫舶。

在繼承父類(lèi)屬性和方法的同時(shí)烂瘫,每個(gè)子類(lèi)也都可以有自己的個(gè)性,在父類(lèi)的基礎(chǔ)上擴(kuò)展自己的功能奇适。前面其實(shí)已經(jīng)提到,當(dāng)功能擴(kuò)展時(shí)芦鳍,子類(lèi)盡量不要重寫(xiě)父類(lèi)的方法嚷往,而是另寫(xiě)一個(gè)方法,所以對(duì)上面的代碼加以更改柠衅,使其符合里氏替換原則皮仁,代碼如下:

public class C {
    public int func(int a, int b) {
        return a + b;
    }
}

public class C1 extends C {
    public int func2(int a, int b) {
        return a - b;
    }
}

public class Client {
    public static void main(String[] args) {
        C1 c = new C1();
        //運(yùn)行結(jié)果:2-1=1
        System.out.println("2-1=" + c.func2(2, 1));
    }
}
  • 當(dāng)子類(lèi)覆蓋或?qū)崿F(xiàn)父類(lèi)的方法時(shí),方法的前置條件(即方法的形參)要比父類(lèi)方法的輸入?yún)?shù)更寬松。

面向?qū)ο蟮木幊檀恚渲欣^承有個(gè)大原則趋急,任何子類(lèi)的對(duì)象都可以當(dāng)成父類(lèi)的對(duì)象使用。
如有父類(lèi)為人類(lèi)势誊,可以使用一般的槍?zhuān)芯祛?lèi)呜达,可以使用任何的槍。

class Person {
    void shoot(SimpleGun simpleGun);
}

class Police extends Person {
    void shoot(Gun gun);
}

其中SimpleGun extends Gun粟耻。
這樣的話(huà)任何警察類(lèi)的對(duì)象都可以被當(dāng)做人類(lèi)來(lái)使用查近。
也就是說(shuō)警察類(lèi)既然會(huì)使用任何的槍?zhuān)?dāng)然可以使用一般的槍。

Person person = new Police();
person.shoot(simpleGun);

而如果反過(guò)來(lái)挤忙,普通人可以使用任何搶?zhuān)熘荒苁褂靡话銟尅?/p>

class Person {
    void shoot(Gun gun);
}

class Police extends Person {
    void shoot(SimpleGun simpleGun);
}

這樣的話(huà)就不合理了霜威,既然警察是人類(lèi)的一個(gè)子類(lèi),所以警察也是人類(lèi)册烈,既然是人類(lèi)就應(yīng)該能使用人類(lèi)的方法戈泼,也就是使用任何的槍?zhuān)梢愿鶕?jù)上面的定義,反而警察類(lèi)的能力還變小了赏僧。
所以大猛,子類(lèi)的能力必須大于等于父類(lèi),即父類(lèi)可以使用的方法次哈,子類(lèi)都可以使用胎署。

  • 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類(lèi)更嚴(yán)格窑滞。

返回值也是同樣的道理琼牧。
假設(shè)一個(gè)父類(lèi)方法返回一個(gè)List,子類(lèi)返回一個(gè)ArrayList哀卫,這當(dāng)然可以巨坊。
如果父類(lèi)方法返回一個(gè)ArrayList,子類(lèi)返回一個(gè)List此改,就說(shuō)不通了趾撵。
這里子類(lèi)返回值的能力是比父類(lèi)小的。

還有拋出異常的情況共啃。
任何子類(lèi)方法可以聲明拋出父類(lèi)方法聲明異常的子類(lèi)占调。
而不能聲明拋出父類(lèi)沒(méi)有聲明的異常。

這一切都是為了移剪,任何子類(lèi)的對(duì)象都可以當(dāng)做父類(lèi)使用究珊。

  1. 依賴(lài)倒轉(zhuǎn)原則(Dependence Inversion Principle)

這個(gè)是開(kāi)閉原則的基礎(chǔ),具體內(nèi)容:針對(duì)接口編程纵苛,依賴(lài)于抽象而不依賴(lài)于具體剿涮。

高層模塊不應(yīng)該依賴(lài)低層模塊言津,二者都應(yīng)該依賴(lài)其抽象;抽象不應(yīng)該依賴(lài)細(xì)節(jié)取试;細(xì)節(jié)應(yīng)該依賴(lài)抽象悬槽。

類(lèi)A直接依賴(lài)類(lèi)B,假如要將類(lèi)A改為依賴(lài)類(lèi)C瞬浓,則必須通過(guò)修改類(lèi)A的代碼來(lái)達(dá)成初婆。這種場(chǎng)景下,類(lèi)A一般是高層模塊瑟蜈,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯烟逊;類(lèi)B和類(lèi)C是低層模塊,負(fù)責(zé)基本的原子操作铺根;假如修改類(lèi)A宪躯,會(huì)給程序帶來(lái)不必要的風(fēng)險(xiǎn)。

將類(lèi)A修改為依賴(lài)接口I位迂,類(lèi)B和類(lèi)C各自實(shí)現(xiàn)接口I访雪,類(lèi)A通過(guò)接口I間接與類(lèi)B或者類(lèi)C發(fā)生聯(lián)系,則會(huì)大大降低修改類(lèi)A的幾率掂林。

依賴(lài)倒置原則基于這樣一個(gè)事實(shí):相對(duì)于細(xì)節(jié)的多變性臣缀,抽象的東西要穩(wěn)定的多。以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定的多泻帮。在Java中精置,抽象指的是接口或者抽象類(lèi),細(xì)節(jié)就是具體的實(shí)現(xiàn)類(lèi)锣杂,使用接口或者抽象類(lèi)的目的是制定好規(guī)范和契約脂倦,而不去涉及任何具體的操作,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類(lèi)去完成元莫。

依賴(lài)倒置原則的核心思想是面向接口編程赖阻,我們依舊用一個(gè)例子來(lái)說(shuō)明面向接口編程比相對(duì)于面向?qū)崿F(xiàn)編程好在什么地方。場(chǎng)景是這樣的踱蠢,母親給孩子講故事火欧,只要給她一本書(shū),她就可以照著書(shū)給孩子講故事了茎截。代碼如下:

class Book{  
    public String getContent(){  
        return "很久很久以前有一個(gè)阿拉伯的故事……";  
    }  
}  
  
class Mother{  
    public void narrate(Book book){  
        System.out.println("媽媽開(kāi)始講故事");  
        System.out.println(book.getContent());  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        Mother mother = new Mother();  
        mother.narrate(new Book());  
    }  
} 

運(yùn)行結(jié)果:
媽媽開(kāi)始講故事
很久很久以前有一個(gè)阿拉伯的故事……

運(yùn)行良好苇侵,假如有一天,需求變成這樣:不是給書(shū)而是給一份報(bào)紙企锌,讓這位母親講一下報(bào)紙上的故事衅檀,報(bào)紙的代碼如下:

class Newspaper{  
    public String getContent(){  
        return "林書(shū)豪38+7領(lǐng)導(dǎo)尼克斯擊敗湖人……";  
    }  
}

這位母親卻辦不到,因?yàn)樗尤徊粫?huì)讀報(bào)紙上的故事霎俩,這太荒唐了哀军,只是將書(shū)換成報(bào)紙,居然必須要修改Mother才能讀打却。假如以后需求換成雜志呢杉适?換成網(wǎng)頁(yè)呢?還要不斷地修改Mother柳击,這顯然不是好的設(shè)計(jì)猿推。原因就是Mother與Book之間的耦合性太高了,必須降低他們之間的耦合度才行捌肴。

我們引入一個(gè)抽象的接口IReader蹬叭。讀物,只要是帶字的都屬于讀物:

interface IReader{
    public String getContent();
}

Mother類(lèi)與接口IReader發(fā)生依賴(lài)關(guān)系状知,而B(niǎo)ook和Newspaper都屬于讀物的范疇秽五,他們各自都去實(shí)現(xiàn)IReader接口,這樣就符合依賴(lài)倒置原則了饥悴,代碼修改為:

class Newspaper implements IReader {
    public String getContent(){
        return "林書(shū)豪17+9助尼克斯擊敗老鷹……";
    }
}

class Book implements IReader{
    public String getContent(){
        return "很久很久以前有一個(gè)阿拉伯的故事……";
    }
}
  
class Mother{
    public void narrate(IReader reader){
        System.out.println("媽媽開(kāi)始講故事");
        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ùn)行結(jié)果:
媽媽開(kāi)始講故事
很久很久以前有一個(gè)阿拉伯的故事……
媽媽開(kāi)始講故事
林書(shū)豪17+9助尼克斯擊敗老鷹……

這樣修改后坦喘,無(wú)論以后怎樣擴(kuò)展Client類(lèi),都不需要再修改Mother類(lèi)了西设。這只是一個(gè)簡(jiǎn)單的例子瓣铣,實(shí)際情況中,代表高層模塊的Mother類(lèi)將負(fù)責(zé)完成主要的業(yè)務(wù)邏輯贷揽,一旦需要對(duì)它進(jìn)行修改棠笑,引入錯(cuò)誤的風(fēng)險(xiǎn)極大。所以遵循依賴(lài)倒置原則可以降低類(lèi)之間的耦合性禽绪,提高系統(tǒng)的穩(wěn)定性蓖救,降低修改程序造成的風(fēng)險(xiǎn)。

采用依賴(lài)倒置原則給多人并行開(kāi)發(fā)帶來(lái)了極大的便利丐一,比如上例中藻糖,原本Mother類(lèi)與Book類(lèi)直接耦合時(shí),Mother類(lèi)必須等Book類(lèi)編碼完成后才可以進(jìn)行編碼库车,因?yàn)镸other類(lèi)依賴(lài)于Book類(lèi)巨柒。修改后的程序則可以同時(shí)開(kāi)工,互不影響柠衍,因?yàn)镸other與Book類(lèi)一點(diǎn)關(guān)系也沒(méi)有洋满。參與協(xié)作開(kāi)發(fā)的人越多、項(xiàng)目越龐大珍坊,采用依賴(lài)導(dǎo)致原則的意義就越重大∥矗現(xiàn)在很流行的TDD開(kāi)發(fā)模式就是依賴(lài)倒置原則最成功的應(yīng)用。

傳遞依賴(lài)關(guān)系有三種方式阵漏,以上的例子中使用的方法是接口傳遞驻民,另外還有兩種傳遞方式:構(gòu)造方法傳遞setter方法傳遞翻具,相信用過(guò)spring框架的,對(duì)依賴(lài)的傳遞方式一定不會(huì)陌生回还。在實(shí)際編程中裆泳,我們一般需要做到如下3點(diǎn):
低層模塊盡量都要有抽象類(lèi)或接口,或者兩者都有柠硕。
變量的聲明類(lèi)型盡量是抽象類(lèi)或接口工禾。
使用繼承時(shí)遵循里氏替換原則。

依賴(lài)倒置原則的核心就是要我們面向接口編程蝗柔,理解了面向接口編程闻葵,也就理解了依賴(lài)倒置。

  1. 接口隔離原則(Interface Segregation Principle)

這個(gè)原則的意思是:使用多個(gè)隔離的接口癣丧,比使用單個(gè)接口要好槽畔。

類(lèi)A通過(guò)接口I依賴(lài)類(lèi)B,類(lèi)C通過(guò)接口I依賴(lài)類(lèi)D坎缭,如果接口I對(duì)于類(lèi)A和類(lèi)B來(lái)說(shuō)不是最小接口竟痰,則類(lèi)B和類(lèi)D必須去實(shí)現(xiàn)他們不需要的方法。

將臃腫的接口I拆分為獨(dú)立的幾個(gè)接口掏呼,類(lèi)A和類(lèi)C分別與他們需要的接口建立依賴(lài)關(guān)系坏快。也就是采用接口隔離原則。

未遵循接口隔離原則的設(shè)計(jì)

這個(gè)圖的意思是:類(lèi)A依賴(lài)接口I中的方法1憎夷、方法2莽鸿、方法3,類(lèi)B是對(duì)類(lèi)A依賴(lài)的實(shí)現(xiàn)拾给。類(lèi)C依賴(lài)接口I中的方法1祥得、方法4、方法5蒋得,類(lèi)D是對(duì)類(lèi)C依賴(lài)的實(shí)現(xiàn)级及。對(duì)于類(lèi)B和類(lèi)D來(lái)說(shuō),雖然他們都存在著用不到的方法(也就是圖中紅色字體標(biāo)記的方法)额衙,但由于實(shí)現(xiàn)了接口I饮焦,所以也必須要實(shí)現(xiàn)這些用不到的方法。

可以看到窍侧,如果接口過(guò)于臃腫县踢,只要接口中出現(xiàn)的方法,不管對(duì)依賴(lài)于它的類(lèi)有沒(méi)有用處伟件,實(shí)現(xiàn)類(lèi)中都必須去實(shí)現(xiàn)這些方法硼啤,這顯然不是好的設(shè)計(jì)。如果將這個(gè)設(shè)計(jì)修改為符合接口隔離原則斧账,就必須對(duì)接口I進(jìn)行拆分谴返。在這里我們將原有的接口I拆分為三個(gè)接口煞肾,拆分后的設(shè)計(jì)下圖所示:

遵循接口隔離原則的設(shè)計(jì)

接口隔離原則的含義是:建立單一接口,不要建立龐大臃腫的接口嗓袱,盡量細(xì)化接口扯旷,接口中的方法盡量少。也就是說(shuō)索抓,我們要為各個(gè)類(lèi)建立專(zhuān)用的接口,而不要試圖去建立一個(gè)很龐大的接口供所有依賴(lài)它的類(lèi)去調(diào)用毯炮。本文例子中逼肯,將一個(gè)龐大的接口變更為3個(gè)專(zhuān)用的接口所采用的就是接口隔離原則。在程序設(shè)計(jì)中桃煎,依賴(lài)幾個(gè)專(zhuān)用的接口要比依賴(lài)一個(gè)綜合的接口更靈活篮幢。接口是設(shè)計(jì)時(shí)對(duì)外部設(shè)定的“契約”,通過(guò)分散定義多個(gè)接口为迈,可以預(yù)防外來(lái)變更的擴(kuò)散三椿,提高系統(tǒng)的靈活性和可維護(hù)性。

說(shuō)到這里葫辐,很多人會(huì)覺(jué)的接口隔離原則跟之前的單一職責(zé)原則(后面會(huì)細(xì)說(shuō))很相似搜锰,其實(shí)不然。其一耿战,單一職責(zé)原則原注重的是職責(zé)蛋叼;而接口隔離原則注重對(duì)接口依賴(lài)的隔離。其二剂陡,單一職責(zé)原則主要是約束類(lèi)狈涮,其次才是接口和方法,它針對(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ù)雜化带射。所以一定要適度。
  • 為依賴(lài)接口的類(lèi)定制服務(wù)循狰,只暴露給調(diào)用的類(lèi)它需要的方法窟社,它不需要的方法則隱藏起來(lái)券勺。只有專(zhuān)注地為一個(gè)模塊提供定制服務(wù),才能建立最小的依賴(lài)關(guān)系灿里。
  • 提高內(nèi)聚关炼,減少對(duì)外交互。使接口用最少的方法去完成最多的事情匣吊。

運(yùn)用接口隔離原則儒拂,一定要適度,接口設(shè)計(jì)的過(guò)大或過(guò)小都不好色鸳。設(shè)計(jì)接口的時(shí)候社痛,只有多花些時(shí)間去思考和籌劃,才能準(zhǔn)確地實(shí)踐這一原則命雀。

  1. 迪米特法則(最少知道原則)(Demeter Principle)

為什么叫最少知道原則蒜哀,就是說(shuō):一個(gè)實(shí)體應(yīng)當(dāng)盡量少的與其他實(shí)體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對(duì)獨(dú)立吏砂。

狹義的迪米特法則定義:也叫最少知識(shí)原則(LKP撵儿,Least Knowledge Principle)。如果兩個(gè)類(lèi)不必彼此直接通信狐血,那么這兩個(gè)類(lèi)就不應(yīng)當(dāng)發(fā)生直接的相互作用淀歇。如果其中的一個(gè)類(lèi)需要調(diào)用另一個(gè)類(lèi)的某一個(gè)方法的話(huà),可以通過(guò)第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用氛雪。

廣義的迪米特法則定義:一個(gè)模塊設(shè)計(jì)得好壞的一個(gè)重要的標(biāo)志就是該模塊在多大的程度上將自己的內(nèi)部數(shù)據(jù)與實(shí)現(xiàn)有關(guān)的細(xì)節(jié)隱藏起來(lái)房匆。信息的隱藏非常重要的原因在于,它可以使各個(gè)子系統(tǒng)之間脫耦报亩,從而允許它們獨(dú)立地被開(kāi)發(fā)浴鸿、優(yōu)化、使用閱讀以及修改弦追。

比較形象的說(shuō)明這個(gè)法則的示例:
如果你想讓你的狗狗跑的話(huà)岳链,你會(huì)對(duì)狗狗說(shuō)還是對(duì)四條狗腿說(shuō)?
如果你去店里買(mǎi)東西劲件,你會(huì)把錢(qián)交給店員掸哑,還是會(huì)把錢(qián)包交給店員讓他自己拿?

《設(shè)計(jì)模式其實(shí)很簡(jiǎn)單》這本書(shū)中給了一個(gè)例子,如下:

//某人類(lèi)
public class Somebody{
   //參數(shù)Friend類(lèi)的方法
   public void operation1(Friend friend){
      Stranger stranger=friend.provide();
      stranger.operation3();
   }
}

//朋友類(lèi)Friend
public class Friend{
   //私有數(shù)據(jù)成員零远,某個(gè)陌生人
   private Stranger stranger=new Stranger();
   
   public void operation2(){
   }
   
   public Stranger provide(){
      return stranger;
   }
}

上面的代碼違反了迪米特法則,因?yàn)槿绻oSomebody定義一個(gè)它的直接關(guān)聯(lián)類(lèi)(即一個(gè)Friend的話(huà))苗分,那么這個(gè)類(lèi)應(yīng)該是Friend類(lèi),因?yàn)镾omebody的一個(gè)方法需要Friend類(lèi)作為參數(shù)牵辣,那么這樣以來(lái)摔癣,Stranger類(lèi)就不是Somebody的直接關(guān)聯(lián)類(lèi),但是上面的Somebody的operation1方法卻直接調(diào)用了Stranger類(lèi)的operation3方法,所以這違反了迪米特法則。

所以應(yīng)該在Friend類(lèi)中加一個(gè)方法择浊,它執(zhí)行了對(duì)Stranger類(lèi)的operation3方法的調(diào)用戴卜。然后在Somebody的operation1方法中有friend調(diào)用加入的那個(gè)方法,從而實(shí)現(xiàn)和上面代碼相同的功能琢岩。

代碼如下:

//朋友類(lèi)
public class Friend{
   private Stranger stranger=new Stranger();
   pubic void operation2(){
   }
   
   public void provide(){
      return stranger; 
   }
   
   pubic void forward(){
      stranger.operation3();
   }
}

//某人類(lèi)
public class Somebody{
    public void operation1(Friend friend){
       friend.forward();
    }
}
  1. 合成復(fù)用原則(Composite Reuse Principle)

原則是盡量使用合成/聚合的方式投剥,而不是使用繼承。

  • 對(duì)象的繼承關(guān)系在編譯時(shí)就定義好了担孔,所以無(wú)法在運(yùn)行時(shí)改變從父類(lèi)繼承的子類(lèi)的實(shí)現(xiàn)
  • 子類(lèi)的實(shí)現(xiàn)和它的父類(lèi)有非常緊密的依賴(lài)關(guān)系江锨,以至于父類(lèi)實(shí)現(xiàn)中的任何變化必然會(huì)導(dǎo)致子類(lèi)發(fā)生變化
  • 當(dāng)你復(fù)用子類(lèi)的時(shí)候,如果繼承下來(lái)的實(shí)現(xiàn)不適合解決新的問(wèn)題糕篇,則父類(lèi)必須重寫(xiě)或者被其它更適合的類(lèi)所替換
    這種依賴(lài)關(guān)系限制了靈活性泳桦,并最終限制了復(fù)用性
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娩缰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谒府,老刑警劉巖拼坎,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異完疫,居然都是意外死亡泰鸡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)壳鹤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)盛龄,“玉大人,你說(shuō)我怎么就攤上這事芳誓∮嗖埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵锹淌,是天一觀的道長(zhǎng)匿值。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赂摆,這世上最難降的妖魔是什么挟憔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮烟号,結(jié)果婚禮上绊谭,老公的妹妹穿的比我還像新娘。我一直安慰自己汪拥,他們只是感情好达传,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般趟大。 火紅的嫁衣襯著肌膚如雪鹤树。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天逊朽,我揣著相機(jī)與錄音罕伯,去河邊找鬼。 笑死叽讳,一個(gè)胖子當(dāng)著我的面吹牛追他,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岛蚤,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼邑狸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梨与!你這毒婦竟也來(lái)了笑撞?” 一聲冷哼從身側(cè)響起柿汛,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤初澎,失蹤者是張志新(化名)和其女友劉穎腮郊,沒(méi)想到半個(gè)月后嫌松,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扭勉,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昭灵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年贿讹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了渐逃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡民褂,死狀恐怖茄菊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赊堪,我是刑警寧澤面殖,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站哭廉,受9級(jí)特大地震影響畜普,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜群叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一吃挑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧街立,春花似錦舶衬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春虽画,著一層夾襖步出監(jiān)牢的瞬間舞蔽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工码撰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渗柿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓脖岛,卻偏偏與公主長(zhǎng)得像朵栖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柴梆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

  • 設(shè)計(jì)模式基本原則 開(kāi)放-封閉原則(OCP)陨溅,是說(shuō)軟件實(shí)體(類(lèi)、模塊绍在、函數(shù)等等)應(yīng)該可以拓展门扇,但是不可修改。開(kāi)-閉原...
    西山薄涼閱讀 3,753評(píng)論 3 13
  • 設(shè)計(jì)模式匯總 一偿渡、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用悯嗓、多...
    MinoyJet閱讀 3,903評(píng)論 1 15
  • 目錄: 設(shè)計(jì)模式六大原則(1):?jiǎn)我宦氊?zé)原則 設(shè)計(jì)模式六大原則(2):里氏替換原則 設(shè)計(jì)模式六大原則(3):依賴(lài)倒...
    加油小杜閱讀 719評(píng)論 0 1
  • 設(shè)計(jì)模式六大原則 設(shè)計(jì)模式六大原則(1):?jiǎn)我宦氊?zé)原則 定義:不要存在多于一個(gè)導(dǎo)致類(lèi)變更的原因。通俗的說(shuō)卸察,即一個(gè)類(lèi)...
    viva158閱讀 763評(píng)論 0 1
  • title: 設(shè)計(jì)模式簡(jiǎn)介categories: 設(shè)計(jì)模式tags: 設(shè)計(jì)模式date: 2017-05-03 0...
    九命丿相柳閱讀 581評(píng)論 0 0