面向?qū)ο笤O(shè)計(jì)的七大原則

1.開閉原則 - Open Close Principle(OCP)

1)定義
  • 一個(gè)軟件實(shí)體如類奸鬓、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開放充坑,對(duì)修改關(guān)閉
  • Software entities like classes,modules and functions should be open for extension but closed for modifications.
2)基本概念
  • 開:對(duì)擴(kuò)展開放肩刃,支持方便的擴(kuò)展
  • 閉:對(duì)修修改關(guān)閉,嚴(yán)格限制對(duì)已有的內(nèi)容修改
  • 說(shuō)明:一個(gè)軟件產(chǎn)品只要在生命周期內(nèi),都會(huì)發(fā)生變化,即然變化是一個(gè)事實(shí)情连,我們就應(yīng)該在設(shè)計(jì)時(shí)盡量適應(yīng)這些變化,以提高項(xiàng)目的穩(wěn)定性和靈活性览效,真正實(shí)現(xiàn)“擁抱變化”却舀。開閉原則告訴我們應(yīng)盡量通過(guò)擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化虫几,而不是通過(guò)修改現(xiàn)有代碼來(lái)完成變化,它是為軟件實(shí)體的未來(lái)事件而制定的對(duì)現(xiàn)行開發(fā)設(shè)計(jì)進(jìn)行約束的一個(gè)原則
3)優(yōu)點(diǎn)
  • 提高系統(tǒng)的靈活性挽拔、可復(fù)用性和可維護(hù)性
4)示例:現(xiàn)在辆脸,以課程為例說(shuō)明什么是開閉原則
/**
* 定義課程接口
*/
public interface ICourse {
    String getName();  // 獲取課程名稱
    Double getPrice(); // 獲取課程價(jià)格
    Integer getType(); // 獲取課程類型
}

/**
 * 英語(yǔ)課程接口實(shí)現(xiàn)
 */
public class EnglishCourse implements ICourse {

    private String name;
    private Double price;
    private Integer type;

    public EnglishCourse(String name, Double price, Integer type) {
        this.name = name;
        this.price = price;
        this.type = type;
    }

    @Override
    public String getName() {
        return null;
    }

    @Override
    public Double getPrice() {
        return null;
    }

    @Override
    public Integer getType() {
        return null;
    }
}

// 測(cè)試
public class Main {
    public static void main(String[] args) {
        ICourse course = new EnglishCourse("小學(xué)英語(yǔ)", 199D, "Mr.Zhang");
        System.out.println(
                "課程名字:"+course.getName() + " " +
                "課程價(jià)格:"+course.getPrice() + " " +
                "課程作者:"+course.getAuthor()
        );
    }
}

項(xiàng)目上線,課程正常銷售篱昔,但是我們產(chǎn)品需要做些活動(dòng)來(lái)促進(jìn)銷售每强,比如:打折。那么問(wèn)題來(lái)了:打折這一動(dòng)作就是一個(gè)變化州刽,而我們要做的就是擁抱變化,現(xiàn)在開始考慮如何解決這個(gè)問(wèn)題浪箭,可以考慮下面三種方案:

1)修改接口

  • 在之前的課程接口中添加一個(gè)方法 getSalePrice() 專門用來(lái)獲取打折后的價(jià)格穗椅;
  • 如果這樣修改就會(huì)產(chǎn)生兩個(gè)問(wèn)題,所以此方案否定
    ? (1) ICourse 接口不應(yīng)該被經(jīng)常修改奶栖,否則接口作為契約的作用就失去了
    ? (2) 并不是所有的課程都需要打折匹表,加入還有語(yǔ)文課,數(shù)學(xué)課等都實(shí)現(xiàn)了這一接口宣鄙,但是只有英語(yǔ)課打折袍镀,與實(shí)際業(yè)務(wù)不符
public interface ICourse {

    // 獲取課程名稱
    String getName();

    // 獲取課程價(jià)格
    Double getPrice();

    // 獲取課程類型
    String getAuthor();
    
    // 新增:打折接口
    Double getSalePrice();
}

2)修改實(shí)現(xiàn)類

  • 在接口實(shí)現(xiàn)里直接修改 getPrice()方法,此方法會(huì)導(dǎo)致獲取原價(jià)出問(wèn)題冻晤;或添加獲取打折的接口 getSalePrice()苇羡,這樣就會(huì)導(dǎo)致獲取價(jià)格的方法存在兩個(gè),所以這個(gè)方案也否定鼻弧,此方案不貼代碼了设江。

3)通過(guò)擴(kuò)展實(shí)現(xiàn)變化

  • 直接添加一個(gè)子類 SaleEnglishCourse ,重寫 getPrice()方法,這個(gè)方案對(duì)源代碼沒(méi)有影響攘轩,符合開閉原則叉存,所以是可執(zhí)行的方案,代碼如下,代碼如下:
public class SaleEnglishCourse extends EnglishCourse {

    public SaleEnglishCourse(String name, Double price, String author) {
        super(name, price, author);
    }

    @Override
    public Double getPrice() {
        return super.getPrice() * 0.85;
    }
}

綜上所述度帮,如果采用第三種歼捏,即開閉原則,以后再來(lái)個(gè)語(yǔ)文課程笨篷,數(shù)學(xué)課程等等的價(jià)格變動(dòng)都可以采用此方案瞳秽,維護(hù)性極高而且也很靈活

2.單一職責(zé)原則 - Single Responsibility Principle(SRP)

1)定義
  • 不要存在多于一個(gè)導(dǎo)致類變更的原因
  • There should never be more than one reason for a class to change.
2)基本概念
  • 單一職責(zé)是高內(nèi)聚低耦合的一個(gè)體現(xiàn)
  • 說(shuō)明:通俗的講就是一個(gè)類只能負(fù)責(zé)一個(gè)職責(zé),修改一個(gè)類不能影響到別的功能,也就是說(shuō)只有一個(gè)導(dǎo)致該類被修改的原因
3)優(yōu)點(diǎn)
  • 低耦合性,影響范圍小
  • 降低類的復(fù)雜度冕屯,職責(zé)分明寂诱,提高了可讀性
  • 變更引起的風(fēng)險(xiǎn)低,利于維護(hù)
4)示例:現(xiàn)在安聘,以動(dòng)物為例說(shuō)明什么是單一原則

假如說(shuō)痰洒,類 A 負(fù)責(zé)兩個(gè)不同的職責(zé)瓢棒,T1 和 T2,當(dāng)由于職責(zé) T1 需求發(fā)生改變而需要修改類 A 時(shí),有可能會(huì)導(dǎo)致原本運(yùn)行正常的職責(zé) T2 功能發(fā)生改變或出現(xiàn)異常丘喻。為什么會(huì)出現(xiàn)這種問(wèn)題呢脯宿?代碼耦合度太高,實(shí)現(xiàn)復(fù)雜泉粉,簡(jiǎn)單一句話就是:不夠單一连霉。那么現(xiàn)在提出解決方案:分別建立兩個(gè)類 A 和 B ,使 A 完成職責(zé) T1 功能嗡靡,B 完成職責(zé) T2 功能跺撼,這樣在修改 T1 時(shí)就不會(huì)影響 T2 了,反之亦然讨彼。

說(shuō)到單一職責(zé)原則歉井,很多人都會(huì)不屑一顧。因?yàn)樗?jiǎn)單了哈误。稍有經(jīng)驗(yàn)的程序員即使從來(lái)沒(méi)有讀過(guò)設(shè)計(jì)模式哩至、從來(lái)沒(méi)有聽(tīng)說(shuō)過(guò)單一職責(zé)原則,在設(shè)計(jì)軟件時(shí)也會(huì)自覺(jué)的遵守這一重要原則蜜自,因?yàn)檫@是常識(shí)菩貌。在軟件編程中,誰(shuí)也不希望因?yàn)樾薷牧艘粋€(gè)功能導(dǎo)致其他的功能發(fā)生故障重荠。而避免出現(xiàn)這一問(wèn)題的方法便是遵循單一職責(zé)原則箭阶。雖然單一職責(zé)原則如此簡(jiǎn)單,并且被認(rèn)為是常識(shí)晚缩,但是即便是經(jīng)驗(yàn)豐富的程序員寫出的程序尾膊,也會(huì)有違背這一原則的代碼存在。為什么會(huì)出現(xiàn)這種現(xiàn)象呢荞彼?因?yàn)橛新氊?zé)擴(kuò)散冈敛。所謂職責(zé)擴(kuò)散,就是因?yàn)槟撤N原因鸣皂,職責(zé) T 被分化為粒度更細(xì)的職責(zé) T1 和 T2

/**
* 定義動(dòng)物類
*/
public class Animal {
    public void move(String animal){
        System.out.println(animal + "用翅膀飛");
    }
}

/**
 * 第一次測(cè)試
 */
public class Main {
    public static void main(String[] args) {
       Animal animal = new Animal();
       animal.move("麻雀");
       animal.move("老鷹");
       animal.move("鯨魚");
    }
}

經(jīng)過(guò)上面代碼示例發(fā)現(xiàn):麻雀和老鷹會(huì)飛是可以理解的抓谴,但是鯨魚就有點(diǎn)不合常理了,那么我們遵循單一對(duì)代碼進(jìn)行第(一)次修改,代碼如下:

/**
 * 會(huì)飛的動(dòng)物
 */
public class FlyAnimal {
    public void move(String animal){
        System.out.println(animal + "用翅膀飛");
    }
}

/**
 * 在水里的動(dòng)物
 */
public class WaterAnimal {
    public void move(String animal){
        System.out.println(animal + "在水里游泳");
    }
}

/**
 * 測(cè)試
 */
public class Main {
    public static void main(String[] args) {
       FlyAnimal flyAnimal = new FlyAnimal();
       flyAnimal.move("麻雀");
       flyAnimal.move("老鷹");

       WaterAnimal waterAnimal = new WaterAnimal();
        waterAnimal.move("鯨魚");
    } 
}

遵循單一原則發(fā)現(xiàn)確實(shí)是職責(zé)單一了寞缝,但是我們會(huì)發(fā)現(xiàn)如果這樣修改花銷是很大的癌压,除了將原來(lái)的類分解之外還需要修改客戶端代碼。如果我們不去遵循單一原則荆陆,而是直接在原有代碼進(jìn)行第(二)次修改滩届,代碼如下:

/**
 * 直接修改源代碼
 */
public class Animal {
    public void move(String animal){
        if ("鯨魚".equals(animal)) {
            System.out.println(animal + "在水里游泳");
        } else {
            System.out.println(animal + "用翅膀飛");
        }
    }
}

/**
 * 第三次測(cè)試
 */
public class Main {
    public static void main(String[] args) {
       Animal animal = new Animal();
       animal.move("麻雀");
       animal.move("老鷹");
       animal.move("鯨魚");
    }
}

直接修改源碼確實(shí)簡(jiǎn)單很多,但是卻存在巨大的隱患被啼,加入需求變了:將在水里的動(dòng)物分為淡水的和海水的帜消,那么又要繼續(xù)修改 Animal 類的 move() 方法棠枉,這也就對(duì)會(huì)飛的動(dòng)物造成了一定的風(fēng)險(xiǎn)买优,所以我們繼續(xù)摒棄單一原則對(duì)代碼進(jìn)行第(三)次修改牵啦,代碼如下:

public class Animal {
    public void move(String animal){
        System.out.println(animal + "在水里游泳");
    }
    public void moveA(String animal){
        System.out.println(animal + "用翅膀飛");
    }
}

public class Main {
    public static void main(String[] args) {
       Animal animal = new Animal();
       animal.move("麻雀");
       animal.move("老鷹");
       animal.moveA("鯨魚");
    }
}

在最后一種修改中可以看到,這種修改方式?jīng)]有改動(dòng)原來(lái)的方法犀被,而是在類中新加了一個(gè)方法娄猫,這樣雖然也違背了單一職責(zé)原則贱除,但在方法級(jí)別上卻是符合單一職責(zé)原則的,因?yàn)樗](méi)有動(dòng)原來(lái)方法的代碼媳溺。綜上所述月幌,這三種方式各有優(yōu)缺點(diǎn),那么在實(shí)際編程中悬蔽,采用哪一中呢飞醉?結(jié)論:只有邏輯足夠簡(jiǎn)單,才可以在代碼級(jí)別上違反單一職責(zé)原則屯阀;只有類中方法數(shù)量足夠少,才可以在方法級(jí)別上違反單一職責(zé)原則;

3.里士替換原則 - Liskov Substitution Principle(LSP)

1)定義
  • 定義一:所有引用基類的地方必須能透明地使用其子類的對(duì)象轴术。
  • 定義二:如果對(duì)每一個(gè)類型為 T1的對(duì)象 o1难衰,都有類型為 T2 的對(duì)象o2,使得以 T1定義的所有程序 P 在所有的對(duì)象 o1 都代換成 o2 時(shí)逗栽,程序 P 的行為沒(méi)有發(fā)生變化盖袭,那么類型 T2 是類型 T1 的子類型。
  • Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
2)基本概念
  • 強(qiáng)調(diào)的是設(shè)計(jì)和實(shí)現(xiàn)要依賴于抽象而非具體彼宠;子類只能去擴(kuò)展基類鳄虱,而不是隱藏或者覆蓋基類它包含以下4層含義

1)子類可以實(shí)現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法凭峡。
2)子類中可以增加自己特有的方法拙已。
3)當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松摧冀。
4)當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí)倍踪,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。

3)優(yōu)點(diǎn)
  • 開閉原則的體現(xiàn)索昂,約束繼承泛濫
  • 提高系統(tǒng)的健壯性建车、擴(kuò)展性和兼容性
4)示例:

代碼講解第三個(gè)概念 :
當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松

public class ParentClazz {
    public void say(CharSequence str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(String str) {
        System.out.println("child execute say " + str);
    }
}

/**
 * 測(cè)試
 */
public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");

    }
}

執(zhí)行結(jié)果:
parent execute say hello
child execute say hello

以上代碼中我們并沒(méi)有重寫父類的方法椒惨,只是重載了同名方法缤至,具體的區(qū)別是:子類的參數(shù) String 實(shí)現(xiàn)了父類的參數(shù) CharSequence。此時(shí)執(zhí)行了子類方法康谆,在實(shí)際開發(fā)中领斥,通常這不是我們希望的嫉到,父類一般是抽象類,子類才是具體的實(shí)現(xiàn)類戒突,如果在方法調(diào)用時(shí)傳遞一個(gè)實(shí)現(xiàn)的子類可能就會(huì)產(chǎn)生非預(yù)期的結(jié)果屯碴,引起邏輯錯(cuò)誤,根據(jù)里士替換原則的子類的輸入?yún)?shù)要寬于或者等于父類的輸入?yún)?shù)膊存,我們可以修改父類參數(shù)為String导而,子類采用更寬松的 CharSequence,如果你想讓子類的方法運(yùn)行隔崎,就必須覆寫父類的方法今艺。代碼如下:

public class ParentClazz {
    public void say(String str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(CharSequence str) {
        System.out.println("child execute say " + str);
    }
}

public class Main {
    public static void main(String[] args) {
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");
    }
}

執(zhí)行結(jié)果:
parent execute say hello
parent execute say hello

代碼講解第四個(gè)概念 :

public abstract class Father {
    public abstract Map hello();
}

public class Son extends Father {
    @Override
    public Map hello() {
        HashMap map = new HashMap();
        System.out.println("son execute");
        return map;
    }
}

public class Main {
    public static void main(String[] args) {
        Father father = new Son();
        father.hello();
    }
}

執(zhí)行結(jié)果:
son execute

4.依賴倒置原則 - Dependence Inversion Principle(DIP)

1)定義
  • 高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象爵卒;抽象不應(yīng)該依賴細(xì)節(jié)虚缎,細(xì)節(jié)應(yīng)該依賴抽象。
  • High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
2)基本概念
  • 依賴倒置原則的核心就是要我們面向接口編程钓株,理解了面向接口編程实牡,也就理解了依賴倒置
  • 低層模塊盡量都要有抽象類或接口,或者兩者都有
  • 變量的聲明類型盡量是抽象類或接口
  • 使用繼承時(shí)遵循里氏替換原則
  • 設(shè)計(jì)和實(shí)現(xiàn)要依賴于抽象而非具體轴合。一方面抽象化更符合人的思維習(xí)慣创坞;另一方面,根據(jù)里氏替換原則受葛,可以很容易將原來(lái)的抽象替換為擴(kuò)展后的具體题涨,這樣可以很好的支持開-閉原則
3)優(yōu)點(diǎn)
  • 減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性
  • 降低并行開發(fā)引起的風(fēng)險(xiǎn)
  • 提高代碼的可讀性和可維護(hù)性
4)示例:
public class MrZhang {
    public void study(ChineseCourse course) {
        course.content();
    }
}

public class ChineseCourse {
    public void content() {
        System.out.println("開始學(xué)習(xí)語(yǔ)文課程");
    }
}

public class Main {
    public static void main(String[] args) {
        MrZhang zhang = new MrZhang();
        zhang.study(new ChineseCourse());
    }
}

執(zhí)行結(jié)果:
開始學(xué)習(xí)語(yǔ)文課程

執(zhí)行之后总滩,結(jié)果正常纲堵。那么,考慮一個(gè)問(wèn)題假如此時(shí)要學(xué)習(xí)數(shù)學(xué)課程呢闰渔? 數(shù)學(xué)課程代碼如下:

public class MathCourse {
    public void content() {
        System.out.println("開始學(xué)習(xí)數(shù)學(xué)課程");
    }
}

很顯然席函,MrZhang 無(wú)法學(xué)習(xí),因?yàn)樗荒芙邮? ChineseCourse 澜建,學(xué)習(xí)語(yǔ)文課程向挖。當(dāng)然如果我們修改接受參數(shù)為 MathCourse 的話就可以學(xué)習(xí)了,但是不能學(xué)習(xí)語(yǔ)文炕舵,英語(yǔ)何之,化學(xué)等等。造成此現(xiàn)象的具體原因是:MrZhang 和 ChineseCourse 耦合度太高了咽筋,必須降低耦合度才可以溶推。代碼如下:

public interface ICourse {
    void content();
}

public class MrZhang {
    public void study(ICourse course) {
        course.content();
    }
}

public class ChineseCourse implements ICourse {
    @Override
    public void content() {
        System.out.println("開始學(xué)習(xí)語(yǔ)文課程");
    }
}

public class MathCourse implements ICourse {
    @Override
    public void content() {
        System.out.println("開始學(xué)習(xí)數(shù)學(xué)課程");
    }
}

public class Main {
    public static void main(String[] args) {
        MrZhang zhang = new MrZhang();
        zhang.study(new ChineseCourse());
        zhang.study(new MathCourse());
    }
}

MrZhang 與 ICourse 具有依賴關(guān)系,ChineseCourse 和 MathCourse 屬于課程范疇,并且各自實(shí)現(xiàn)了 ICourse 接口蒜危,這樣就符合了依賴倒置原則虱痕。這樣修改后無(wú)論再怎么擴(kuò)展 Main 類,都不用繼續(xù)修改 MrZhang 了辐赞,MrZhang.java 作為高層模塊就不會(huì)依賴低層模塊的修改而引起變化部翘,減少了修改程序造成的風(fēng)險(xiǎn)。

5.接口隔離原則 - Interface Segration Principle(ISP)

1)定義
  • 定義一:客戶端不應(yīng)該依賴它不需要的接口
  • Clients should not be forced to depend upon interfaces that they don’t use.
  • 定義二:類間的依賴關(guān)系應(yīng)該建立在最小的接口上
  • The dependency of one class to another one should depend on the
    smallest possible interface.
2)基本概念
  • 一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上,通俗的講就是需要什么就提供什么响委,不需要的就不要提供
  • 接口中的方法應(yīng)該盡量少新思,不要使接口過(guò)于臃腫,不要有很多不相關(guān)的邏輯方法
3)優(yōu)點(diǎn)
  • 高內(nèi)聚赘风,低耦合
  • 可讀性高夹囚,易于維護(hù)
4)代碼示例:
public interface IAnimal {
    void eat();
    void talk();
    void fly();
}

public class BirdAnimal implements IAnimal {
    @Override
    public void eat() {
        System.out.println("鳥吃蟲子");
    }

    @Override
    public void talk() {
        //并不是所有的鳥都會(huì)說(shuō)話
    }

    @Override
    public void fly() {
        //并不是所有的鳥都會(huì)飛
    }
}

public class DogAnimal implements IAnimal {
    @Override
    public void eat() {
        System.out.println("狗狗吃飯");
    }

    @Override
    public void talk() {
        //狗不會(huì)說(shuō)話
    }

    @Override
    public void fly() {
        //狗不會(huì)飛
    }
}

通過(guò)上面的代碼發(fā)現(xiàn):狗實(shí)現(xiàn)動(dòng)物接口,必須實(shí)現(xiàn)三個(gè)接口邀窃,根據(jù)常識(shí)我們得知荸哟,第二個(gè)和第三個(gè)接口不一定會(huì)有實(shí)際意義,換句話說(shuō)也就是這個(gè)方法有可能一直不會(huì)被調(diào)用瞬捕。但是就是這樣我們還必須實(shí)現(xiàn)這個(gè)方法鞍历,盡管方法體可以為空,但是這就違反了接口隔離的定義肪虎。我們知道 由于Java類支持實(shí)現(xiàn)多個(gè)接口堰燎,可以很容易的讓類具有多種接口的特征,同時(shí)每個(gè)類可以選擇性地只實(shí)現(xiàn)目標(biāo)接口笋轨,基于此特點(diǎn)我們可以對(duì)功能進(jìn)一步的細(xì)化,編寫一個(gè)或多個(gè)接口赊淑,代碼如下:

public interface IEat {
    void eat();
}

public interface IFly {
    void fly();
}

public interface ITalk {
    void talk();
}

public class DogAnimal implements IEat {
    @Override
    public void eat() {
        System.out.println("狗狗吃飯");
    }
}

public class ParrotAnimal implements IEat, IFly, ITalk {

    @Override
    public void eat() {
        System.out.println("鸚鵡吃東西");
    }

    @Override
    public void fly() {
        System.out.println("鸚鵡吃飛翔");
    }

    @Override
    public void talk() {
        System.out.println("鸚鵡說(shuō)話");
    }
}

說(shuō)到這里爵政,很多人會(huì)覺(jué)的接口隔離原則跟之前的單一職責(zé)原則很相似,其實(shí)不然陶缺。
其一钾挟,單一職責(zé)原則原注重的是職責(zé);而接口隔離原則注重對(duì)接口依賴的隔離饱岸。
其二掺出,單一職責(zé)原則主要是約束類,其次才是接口和方法苫费,它針對(duì)的是程序中的實(shí)現(xiàn)和細(xì)節(jié)汤锨;而接口隔離原則主要約束接口接口,主要針對(duì)抽象百框,針對(duì)程序整體框架的構(gòu)建

接口隔離原則一定要適度使用闲礼,接口設(shè)計(jì)的過(guò)大或過(guò)小都不好,過(guò)分的細(xì)粒度可能造成接口數(shù)量龐大不易于管理

6.迪米特法則/最少知道原則 - Law of Demeter or Least Knowledge Principle(LoD or LKP)

1)定義
  • 一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解
  • 這個(gè)原理的名稱來(lái)源于希臘神話中的農(nóng)業(yè)女神,孤獨(dú)的得墨忒耳柬泽。
2)基本概念
  • 每個(gè)單元對(duì)于其他的單元只能擁有有限的知識(shí):只是與當(dāng)前單元緊密聯(lián)系的單元慎菲;
  • 每個(gè)單元只能和它的朋友交談:不能和陌生單元交談;
  • 只和自己直接的朋友交談锨并。
3)優(yōu)點(diǎn)
  • 使得軟件更好的可維護(hù)性與適應(yīng)性
  • 對(duì)象較少依賴其它對(duì)象的內(nèi)部結(jié)構(gòu)露该,可以改變對(duì)象容器(container)而不用改變它的調(diào)用者(caller)
4)詳細(xì)講解:

迪米特法則通俗的來(lái)講,就是一個(gè)類對(duì)自己依賴的類知道的越少越好第煮。也就是說(shuō)解幼,對(duì)于被依賴的類來(lái)說(shuō),無(wú)論邏輯多么復(fù)雜空盼,都盡量地的將邏輯封裝在類的內(nèi)部书幕,對(duì)外除了提供的public方法,不對(duì)外泄漏任何信息揽趾。迪米特法則還有一個(gè)更簡(jiǎn)單的定義:只與直接的朋友通信台汇。首先來(lái)解釋一下什么是直接的朋友:每個(gè)對(duì)象都會(huì)與其他對(duì)象有耦合關(guān)系,只要兩個(gè)對(duì)象之間有耦合關(guān)系篱瞎,我們就說(shuō)這兩個(gè)對(duì)象之間是朋友關(guān)系苟呐。耦合的方式很多,依賴俐筋、關(guān)聯(lián)牵素、組合、聚合等澄者。其中笆呆,我們稱出現(xiàn)成員變量、方法參數(shù)粱挡、方法返回值中的類為直接的朋友赠幕,而出現(xiàn)在局部變量中的類則不是直接的朋友。也就是說(shuō)询筏,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。

代碼舉例:通過(guò)老師要求班長(zhǎng)告知班級(jí)人數(shù)為例逆屡,講解迪米特法則踱讨。先來(lái)看一下違反迪米特法則的設(shè)計(jì)魏蔗,代碼如下

public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Teacher {
    public void call(Monitor monitor) {
        List<Student> sts = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            sts.add(new Student(i + 1, "name" + i));
        }
        monitor.getSize(sts);
    }
}

public class Monitor {
    public void getSize(List list) {
        System.out.println("班級(jí)人數(shù):" + list.size());
    }
}

現(xiàn)在這個(gè)設(shè)計(jì)的主要問(wèn)題出在 Teacher 中,根據(jù)迪米特法則沫勿,只與直接的朋友發(fā)生通信,而 Student 類并不是 Teacher 類的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友)产雹,從邏輯上講 Teacher 只與 Monitor 耦合就行了诫惭,與 Student 并沒(méi)有任何聯(lián)系,這樣設(shè)計(jì)顯然是增加了不必要的耦合蔓挖。按照迪米特法則,應(yīng)該避免類中出現(xiàn)這樣非直接朋友關(guān)系的耦合怨绣。修改后的代碼如下:

public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Teacher {
    public void call(Monitor monitor) {
        monitor.getSize();
    }
}

public class Monitor {
    public void getSize() {
        List<Student> sts = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            sts.add(new Student(i + 1, "name" + i));
        }
        System.out.println("班級(jí)人數(shù)" + sts.size());
    }
}

將Student 從 Teacher 抽掉篮撑,也就達(dá)到了 Student 和 Teacher 的解耦匆瓜,從而符合了迪米特原則。

迪米特法則的初衷是降低類之間的耦合茧妒,由于每個(gè)類都減少了不必要的依賴左冬,因此的確可以降低耦合關(guān)系拇砰。但是凡事都有度,雖然可以避免與非直接的類通信除破,但是要通信皂岔,必然會(huì)通過(guò)一個(gè)“中介”來(lái)發(fā)生聯(lián)系展姐,例如本例中,老師(Teacher)就是通過(guò)班長(zhǎng)(Monitor)這個(gè)“中介”來(lái)與 學(xué)生(Student)發(fā)生聯(lián)系的教馆。過(guò)分的使用迪米特原則土铺,會(huì)產(chǎn)生大量這樣的中介和傳遞類,導(dǎo)致系統(tǒng)復(fù)雜度變大究恤。所以在采用迪米特法則時(shí)要反復(fù)權(quán)衡后德,既做到結(jié)構(gòu)清晰,又要高內(nèi)聚低耦合理张。

7.合成/聚合復(fù)用原則 - Composite/Aggregate Reuse Principle(CARP / CRP)

1) 定義
  • 盡量采用組合(contains-a)绵患、聚合(has-a)的方式而不是繼承(is-a)的關(guān)系來(lái)達(dá)到軟件的復(fù)用目的
2)基本概念
  • 如果新對(duì)象的某些功能在別的已經(jīng)創(chuàng)建好的對(duì)象里面已經(jīng)實(shí)現(xiàn)落蝙,那么應(yīng)當(dāng)盡量使用別的對(duì)象提供的功能,使之成為新對(duì)象的一部分赚瘦,而不要再重新創(chuàng)建

組合/聚合的優(yōu)缺點(diǎn):類之間的耦合比較低奏寨,一個(gè)類的變化對(duì)其他類造成的影響比較少病瞳,缺點(diǎn):類的數(shù)量增多實(shí)現(xiàn)起來(lái)比較麻煩

繼承的優(yōu)點(diǎn):由于很多方法父類已經(jīng)實(shí)現(xiàn),子類的實(shí)現(xiàn)會(huì)相對(duì)比較簡(jiǎn)單亲善,缺點(diǎn):將父類暴露給了子類逗柴,一定程度上破壞了封裝性戏溺,父類的改變對(duì)子類影響比較大

3)優(yōu)點(diǎn)
  • 可以降低類與類之間的耦合程度
  • 提高了系統(tǒng)的靈活性
4)講解
public class Person {
    public void talk(String name) {
        System.out.println(name + " say hello");
    }
    public void walk(String name) {
        System.out.println(name + " move");
    }
}

public class Manager extends Person { 
}

public class Employee extends Person {
}

按照組合復(fù)用原則我們應(yīng)該首選組合旷祸,然后才是繼承,使用繼承時(shí)應(yīng)該嚴(yán)格的遵守里氏替換原則骚烧,必須滿足“Is-A”的關(guān)系是才能使用繼承,而組合卻是一種“Has-A”的關(guān)系既峡。導(dǎo)致錯(cuò)誤的使用繼承而不是使用組合的一個(gè)重要原因可能就是錯(cuò)誤的把“Has-A”當(dāng)成了“Is-A”凭戴。

由上沒(méi)看的代碼可以看出,經(jīng)理和員工繼承了人者冤,但實(shí)際中每個(gè)不同的職位擁有不同的角色涉枫,如果我們添加了角色這個(gè)類腐螟,那么繼續(xù)使用繼承的話只能使每個(gè)人只能具有一種角色,這顯然是不合理的衬廷。

備注:Java設(shè)計(jì)模式的橋接模式很好的體現(xiàn)了這一原則

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吗跋,一起剝皮案震驚了整個(gè)濱河市宁昭,隨后出現(xiàn)的幾起案子积仗,更是在濱河造成了極大的恐慌,老刑警劉巖哎迄,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱挚,死亡現(xiàn)場(chǎng)離奇詭異匾灶,居然都是意外死亡租漂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門衬鱼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)憔杨,“玉大人消别,你說(shuō)我怎么就攤上這事∷昃” “怎么了蛇券?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵纠亚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我图呢,道長(zhǎng)啤誊,這世上最難降的妖魔是什么蚊锹? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任牡昆,我火速辦了婚禮,結(jié)果婚禮上柱宦,老公的妹妹穿的比我還像新娘播瞳。我一直安慰自己赢乓,他們只是感情好石窑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布松逊。 她就那樣靜靜地躺著肯夏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烁兰。 梳的紋絲不亂的頭發(fā)上缚柏,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天碟贾,我揣著相機(jī)與錄音袱耽,去河邊找鬼。 笑死史翘,一個(gè)胖子當(dāng)著我的面吹牛冀续,可吹牛的內(nèi)容都是我干的洪唐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼问欠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼顺献!你這毒婦竟也來(lái)了枯怖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤借浊,失蹤者是張志新(化名)和其女友劉穎萝招,沒(méi)想到半個(gè)月后存捺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捌治,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肖油,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年森枪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浑娜。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筋遭,死狀恐怖暴拄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情响驴,我是刑警寧澤那伐,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布罕邀,位于F島的核電站诉探,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏竖席。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一束析、第九天 我趴在偏房一處隱蔽的房頂上張望员寇。 院中可真熱鬧第美,春花似錦、人聲如沸扳缕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)衫樊。三九已至,卻和暖如春载佳,著一層夾襖步出監(jiān)牢的瞬間臀栈,已是汗流浹背权薯。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黍析,地道東北人屎开。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像甩鳄,于是被迫代替她去往敵國(guó)和親额划。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俊戳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 概述 1品抽、軟件的復(fù)用(Reuse)或重用擁有眾多優(yōu)點(diǎn)甜熔,如可以提高軟件的開發(fā)效率腔稀,提高軟件質(zhì)量,節(jié)約開發(fā)成本淡喜,恰當(dāng)?shù)?..
    奮拓達(dá)閱讀 772評(píng)論 0 0
  • 單一職責(zé)原則 (SRP) 全稱 SRP , Single Responsibility Principle 單一職...
    米莉_L閱讀 1,764評(píng)論 2 5
  • 前言 關(guān)于設(shè)計(jì)模式六大設(shè)計(jì)原則的資料網(wǎng)上很多炼团,但感覺(jué)很多地方解釋地都太過(guò)于籠統(tǒng)化瘟芝,特此再總結(jié)一波褥琐。 優(yōu)化第一步-單...
    ghroost閱讀 1,104評(píng)論 0 5
  • 今天下午敌呈,讀了嚴(yán)歌苓的芳華,心里確實(shí)感覺(jué)到酸酸的吭练。那個(gè)時(shí)代析显,人心惶惶,誰(shuí)都不知誰(shuí)會(huì)在不經(jīng)意間犯了什么錯(cuò)浑侥,那個(gè)時(shí)代的...
    朱小祥閱讀 232評(píng)論 1 1
  • 國(guó)家讓民更富強(qiáng)寓落, 為民修渠春季忙。 河水澆灌多產(chǎn)糧史飞, 黨策暖心喜洋洋仰税。
    笑口常開_6135閱讀 411評(píng)論 5 7