設(shè)計(jì)模式七大原則(哪七個(gè))及代碼示例,請看本篇文章

七大原則:

  1. 單一職責(zé)原則;
  2. 接口隔離原則;
  3. 依賴倒轉(zhuǎn)原則;
  4. 里氏替換原則;
  5. 開閉原則ocp;
  6. 迪米特法則;
  7. 合成復(fù)用原則。

設(shè)計(jì)模式其實(shí)包含了面向?qū)ο蟮木瑁庋b、繼承、多態(tài)。

一、單一職責(zé)原則

對于類來說,一個(gè)類應(yīng)該只負(fù)責(zé)一項(xiàng)職責(zé)。

假設(shè)A負(fù)責(zé)兩個(gè)不同的職責(zé)1和2,如果1的內(nèi)容需要改變,影響了2,那可能2會(huì)執(zhí)行錯(cuò)誤,所以需要將A分為兩個(gè)類。

1.1 示例

public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽車");
        vehicle.run("摩托車");
        vehicle.run("飛機(jī)");
    }
}

class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在地上跑");
    }
}

對于一個(gè)完成交通工具的類Vehicle來說,顯然對不同的對象、汽車和飛機(jī),提供的服務(wù)不應(yīng)該都是在地上跑,并且修改之后,肯定會(huì)影響到其中一類對象的功能,所以按照單一職責(zé),那就應(yīng)該拆開,成兩個(gè)類。

1.2 改進(jìn)版本 1

public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("摩托車");
        roadVehicle.run("汽車");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飛機(jī)");
    }
}

class RoadVehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在地上跑");
    }
}
class AirVehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在天上飛");
    }
}

但是這么寫又有了新的問題:那就是類分解的時(shí)候,客戶端代碼也要改,調(diào)用方式改了。因此可以直接更改本來的Vehicle類,變成我們的第三種寫法.

1.3 改進(jìn)版本 2

public class SingleResponsibility3 {
    public static void main(String[] args) {
        Vehicle2 vehicle2 = new Vehicle2();
        vehicle2.runRoad("汽車");
        vehicle2.runWater("輪船");
        vehicle2.runAir("飛機(jī)");
    }
}

class Vehicle2{
    public void runRoad(String vehicle){
        System.out.println(vehicle+"在公路上跑");
    }
    public void runAir(String vehicle){
        System.out.println(vehicle+"在天上飛");
    }
    public void runWater(String vehicle){
        System.out.println(vehicle+"在水里游");
    }
}

這種寫法,顯然更加方便,相比第一種,沒有更改類的聲明方式,只是在類內(nèi)部增加了方法的各司其職,可以看出來,雖然沒有在類級別上嚴(yán)格遵循單一職責(zé)原則,但是在方法級別上嚴(yán)格遵循了單一職責(zé)原則,相比之下比方法2更合適。

1.4 總結(jié)單一職責(zé)原則

  1. 降低類的復(fù)雜度,一個(gè)類只負(fù)責(zé)一項(xiàng)職責(zé)(上面的例子因?yàn)檫^于簡單,所以看起來第三個(gè)寫法更有效);
  2. 提高類的可讀性、可維護(hù)性,降低變更帶來的風(fēng)險(xiǎn)
  3. 通常情況下,我們應(yīng)該遵守這個(gè)職責(zé),只有在邏輯足夠簡單的時(shí)候,才可以在代碼級別違反這個(gè)原則,也就是上面的,改為在方法級別保持單一職責(zé)原則。

二、接口隔離原則

接口隔離(Interface Segregation Principle)

意思是說,如果某一個(gè)類對另一個(gè)類的依賴通過的是接口,那么這個(gè)類對另一個(gè)類的依賴應(yīng)該建立在最小的接口上,如果不是最小接口,則需要拆。

2.1 示例

例如如下的用例圖:

image.png

A 和 B 是 Interface1 的實(shí)現(xiàn)類,所以必須實(shí)現(xiàn)它的 4 個(gè)方法,C 和 D 分別依賴于這個(gè)接口,使用 A 和 B 里面對應(yīng)的方法,但不是全部,C 使用前三個(gè)方法,D 使用后三個(gè)方法,如果按照類圖實(shí)現(xiàn),代碼會(huì)如下所示:

interface Interface1{
    void fuc1();
    void fuc2();
    void fuc3();
    void fuc4();
}

class A implements Interface1{
    public void fuc1() {System.out.println("A實(shí)現(xiàn)了fuc1");}
    public void fuc2() {System.out.println("A實(shí)現(xiàn)了fuc2");}
    public void fuc3() {System.out.println("A實(shí)現(xiàn)了fuc3");}
    public void fuc4() {System.out.println("A實(shí)現(xiàn)了fuc4");}
}

class B implements Interface1{
    public void fuc1() {System.out.println("B實(shí)現(xiàn)了fuc1");}
    public void fuc2() {System.out.println("B實(shí)現(xiàn)了fuc2");}
    public void fuc3() {System.out.println("B實(shí)現(xiàn)了fuc3");}
    public void fuc4() {System.out.println("B實(shí)現(xiàn)了fuc4");}
}
//C通過接口Interface1依賴,使用A這個(gè)實(shí)現(xiàn)類,但是只用A的前兩個(gè)方法
class C {
    public void depend1(Interface1 i){
        i.fuc1();
    }
    public void depend2(Interface1 i){
        i.fuc2();
    }
    public void depend3(Interface1 i){
        i.fuc3();
    }
}
//D通過接口Interface1依賴,使用B這個(gè)實(shí)現(xiàn)類,但是只用B的后兩個(gè)方法
class D {
    public void depend2(Interface1 i){
        i.fuc2();
    }
    public void depend3(Interface1 i){
        i.fuc3();
    }
    public void depend4(Interface1 i){
        i.fuc4();
    }
}

顯然,A 和 B 兩個(gè)實(shí)現(xiàn)類都做了多余的工作,也就是 C 和 D 依賴的這個(gè)接口有些方法是他們不需要的,這個(gè)接口寫的不好。

2.2 改進(jìn)

根據(jù)接口隔離原則,我們應(yīng)該將其拆成滿足最小接口的類型,也就是說多余的我們?nèi)疾粦?yīng)要,所以接口 Interface1 應(yīng)該拆分為三個(gè)接口,接口 1 里面有方法 1 ,接口 2 里面有方法 23,接口 3 里面有方法 4,這樣實(shí)現(xiàn)的時(shí)候A和B兩個(gè)類就更加清晰,修改后的類圖如下:

image

3|0三、依賴倒轉(zhuǎn)原則

interface Interface11{
    void fuc1();
}
interface Interface12{
    void fuc2();
    void fuc3();
}
interface Interface13{
    void fuc4();
}
class A1 implements Interface11,Interface12{
    public void fuc1() {System.out.println("A實(shí)現(xiàn)了接口1的fuc1");}
    public void fuc2() {System.out.println("A實(shí)現(xiàn)了接口2的fuc2");}
    public void fuc3() {System.out.println("A實(shí)現(xiàn)了接口2的fuc3");}
}
class B1 implements Interface12,Interface13{
    public void fuc2() {System.out.println("B實(shí)現(xiàn)了接口2的fuc2");}
    public void fuc3() {System.out.println("B實(shí)現(xiàn)了接口2的fuc3");}
    public void fuc4() {System.out.println("B實(shí)現(xiàn)了接口3的fuc4");}
}
class C1{
    public void depend1(Interface11 i){
        i.fuc1();
    }
    public void depend2(Interface12 i){
        i.fuc2();
    }
    public void depend3(Interface12 i){
        i.fuc3();
    }
}
class D1{
    public void depend2(Interface12 i){
        i.fuc2();
    }
    public void depend3(Interface12 i){
        i.fuc3();
    }
    public void depend4(Interface13 i){
        i.fuc4();
    }
}

這種寫法就是遵循了接口隔離原則

客戶端使用的時(shí)候:

    C1 c = new C1();
    c.depend1(new A1());//C類通過接口依賴(使用)的是A類
    c.depend2(new A1());
    c.depend3(new A1());
    D1 d = new D1();
    d.depend2(new B1());//D類通過接口依賴(使用)的是B類
    d.depend3(new B1());
    d.depend4(new B1());

三、依賴倒轉(zhuǎn)原則

依賴倒轉(zhuǎn)原則 ( Dependence Inversion Principle ) 指的是:

  1. 高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象;
  2. 抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象;
  3. 依賴倒轉(zhuǎn)的核心思想是面向接口編程。

為什么要有依賴倒轉(zhuǎn)原則:主要是因?yàn)?,相對于?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)類去做。

依賴倒轉(zhuǎn)原則需要注意:

  1. 底層模塊盡量都要有抽象類或接口,或者兩者都有,程序的穩(wěn)定性會(huì)更好;
  2. 變量的聲明類型盡量是抽象類或接口,這樣我們的變量引用和實(shí)際對象之間,就存在一個(gè)緩沖層,利于程序擴(kuò)展和優(yōu)化;
  3. 繼承時(shí)遵循里氏替換原則。

例如:有一個(gè)功能,一個(gè)Person類,要有一個(gè)接收消息的功能。

3.1 示例

public class DependencyInversion1 {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}
class Email{
    public String getInfo(){
        return "電子郵件信息";
    }
}
class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

因?yàn)镻erson類需要的接受是一個(gè)功能,消息應(yīng)該是另一個(gè)類,所以還有一個(gè)Email類。那么這種寫法,顯然直接犯在Person類里依賴了Email類,也就是上面所說的”高層模塊依賴底層模塊“。

那么這么寫可能會(huì)帶來哪些問題呢?

對于Person類,接受的這個(gè)動(dòng)作可以擴(kuò)展,如果要接受的不僅僅是Email,是很多短信、微信等信息,那么在新增短信、微信類的同時(shí),Person類里要新增對應(yīng)的方法,改動(dòng)基本是所有的都改動(dòng)。

按照依賴倒轉(zhuǎn)原則,對于Email、短信這些不同的類,應(yīng)該將他們抽象出一個(gè)接口,然后他們實(shí)現(xiàn)接口,這樣的話,對于Person類,他的接受方法,就是對這個(gè)抽象接口的依賴,而不是直接依賴這些不同的實(shí)現(xiàn)類。

3.2 改進(jìn)

public class Dependency1 {
    public static void main(String[] args) {
        Person1 person1 = new Person1();
        person1.receive(new Email1());
    }
}
interface IReceiver{
    public String getInfo();
}
class Email1 implements IReceiver{
    public String getInfo() {
        return "電子郵件信息:";
    }
}
class Person1{
    public void receive(IReceiver receiver){
        System.out.println(receiver.getInfo());
    }
}

在客戶端的調(diào)用方式也是幾乎一樣的,運(yùn)行結(jié)果是一樣的,同時(shí),因?yàn)镻erson依賴的是抽象的接口而不是具體的Email。

當(dāng)我們想要增加一個(gè)接受的類的時(shí)候,平行增加,同時(shí)都去實(shí)現(xiàn)接口里面的方法就可以,Person類不用做改變,那么調(diào)用的時(shí)候傳入?yún)?shù)去變成具體的實(shí)現(xiàn)類就可以。

//擴(kuò)展
class Wechat implements IReceiver{
    public String getInfo() {
        return "微信消息:";
    }
}

那么main方法的客戶端只需要:

person1.receive(new Wechat());

就可以。

3.3 擴(kuò)展:依賴關(guān)系傳遞的三種方式

依賴關(guān)系的傳遞一般有三種方式:

  1. 接口傳遞;
  2. 構(gòu)造方法傳遞;
  3. setter方法傳遞。

第一種

//方式1:通過接口傳遞依賴
interface IOpenandClose{
    public void open(ITV itv);
}
interface ITV{
    public void play();
}
class OpenandClose implements IOpenandClose{
    public void open(ITV itv) {
        itv.play();
    }
}

可以看到,因?yàn)榈谝粋€(gè)接口依賴于第二個(gè)接口,那么實(shí)現(xiàn)第一個(gè)接口的時(shí)候,就需要實(shí)現(xiàn)對應(yīng)的方法,把接口作為參數(shù),實(shí)現(xiàn)了依賴的傳遞。

第二種

//方式2,通過構(gòu)造方法傳遞依賴
interface IOpenandClose2{
    public void open();
}
interface ITV2{
    public void play();
}
class OpenandClose2 implements IOpenandClose2{
    public ITV2 itv2;
    public OpenandClose2(ITV2 itv2){
        this.itv2 = itv2;
    }
    public void open() {
        this.itv2.play();
    }
}

通第一個(gè)接口和第二個(gè)接口雖然沒有直接寫明依賴,但是依賴體現(xiàn)在實(shí)現(xiàn)類里,實(shí)現(xiàn)類里通過構(gòu)造方法傳入一個(gè)參數(shù),才能進(jìn)行open方法的實(shí)現(xiàn),所以在構(gòu)造方法的部分體現(xiàn)了依賴關(guān)系。

第三種

//方式3,通過set方法傳遞依賴
interface IOpenandClose3{
    public void open();
    public void setTV(ITV3 itv3);
}
interface ITV3{
    public void play();
}
class OpenandClose3 implements IOpenandClose3{
    private ITV3 itv3;
    public void setTV(ITV3 itv3) {
        this.itv3 = itv3;
    }
    public void open() {
        this.itv3.play();
    }
}

其實(shí)和第一種類似,不過沒有在open的地方直接依賴,而是分成兩個(gè)步驟,先給聲明的ITV初始化,再進(jìn)行使用,這樣依賴也就傳遞成功了。

使用三種示例的方法,我們都用到一個(gè)ITV的實(shí)現(xiàn)類,實(shí)現(xiàn)一個(gè)play方法,然后調(diào)用開關(guān)這個(gè)類,體現(xiàn)開關(guān)接口的依賴性。比如第一種是

class Sumsang implements ITV{
    public void play() {
        System.out.println("三星電視開機(jī)啦");
    }
}

然后在主方法里調(diào)用就是:

OpenandClose close = new OpenandClose();
close.open(new Sumsang());

因?yàn)槭褂瞄_關(guān)機(jī)的時(shí)候,這個(gè)方法就是通過接口參數(shù)才能調(diào)用,所以這是第一種接口傳遞。

第二種就只用:

OpenandClose2 close2 = new OpenandClose2(new Sony());
close2.open();

雖然open沒有參數(shù),但是依賴是通過構(gòu)造方法傳遞的。

第三種情況:

OpenandClose3 close3 = new OpenandClose3();
close3.setTV(new Xiaomi());//如果不先set,就會(huì)報(bào)空指針close3.open();

四、里氏替換原則

面向?qū)ο笾欣^承的問題:

  1. 父類實(shí)現(xiàn)好的方法,實(shí)際上設(shè)定了規(guī)范,雖然不強(qiáng)制要求子類都要遵守,到那時(shí)如果子類對這些方法進(jìn)行了修改,會(huì)對整個(gè)繼承體系造成破壞;
  2. 如果使用繼承會(huì)給程序帶來入侵性,使得移植性降低,增加對象之間的耦合性,如果一個(gè)類被其他的類所繼承,則當(dāng)這個(gè)類需要修改時(shí),會(huì)影響所有子類;
  3. 那么,如何在編程中正確使用繼承呢?=> 里氏替換原則

里氏替換

  1. 里氏替換原則:如果每個(gè)類型 T1 的對象 o1 ,都有類型 T2 的對象 o2,使得以 T1 定義的所有程序 P 在所有的對象 o1 都換成 o2 時(shí),程序 P 的行為沒有發(fā)生變化,那么類型 T2 是類型 T1 的子類型,也就是說,引用基類(父類)的地方必須能透明的使用其子類(派生類)的對象。
  2. 使用繼承的時(shí)候,遵循里氏替換原則,也就是盡量不要重寫父類的方法;
  3. 這個(gè)原則告訴我們,繼承讓兩個(gè)類的耦合性增強(qiáng)了,適當(dāng)情況下,可以通過聚合、組合、依賴來解決問題。

如何理解這個(gè)原則呢,假如一個(gè)父類是 A,B extends A,結(jié)果把 A 類的所有方法都重寫了,那肯定A類的對象所有行為都變化了,另一方面,B 還要繼承 A 類純屬有病。適當(dāng)?shù)?,A 和 B 更適合于,繼承同一個(gè)更加基礎(chǔ)的類 C,重新整理,這樣解決這個(gè)問題會(huì)比較合適。

4.1 示例

例如:

class A{
    public int func1(int a, int b){
        return a - b;
    }
}
class B extends A{
    public int func1(int a, int b){
        return a + b;
    }
    public int func2(int a, int b){
        return func1(a, b)+9;
    }
}

不管是無意還是有意,B 繼承 A 的時(shí)候把 func1 重寫了。

顯然,這樣 B 以為被正常調(diào)用的時(shí)候,求a-b的 func1 ,卻輸出了和調(diào)用 a 的 func 1不一樣的結(jié)果。(可能例子不是很恰當(dāng),但是如果更復(fù)雜的情況下,調(diào)用一個(gè)子類的某一個(gè)方法,方法名是一樣的,肯定會(huì)認(rèn)為功能是一樣的)

實(shí)際開發(fā)過程中,就是因?yàn)橐恍┲貙懜割惙椒▉硗瓿尚鹿δ艿牟僮?,讓整個(gè)繼承體系的復(fù)用性變差,特別是用到多態(tài)比較多的時(shí)候。

通用的做法就是:原來的父類和子類都繼承一個(gè)更通俗的基類,原有的繼承關(guān)系去掉,采用依賴、聚合、組合等關(guān)系來替代

4.2 改進(jìn)

//更加基礎(chǔ)的類
class Base{

}
class A1 extends Base{
    public int func1(int a, int b){
        return a - b;
    }
}
class B1 extends Base{
    public int func1(int a, int b){
        return a + b;
    }
    public int func2(int a, int b){
        return func1(a, b)+9;
    }
    //如果b要使用到A的方法,使用組合的關(guān)系
    private A1 a1 = new A1();
    //仍然使用A的方法
    public int func3(int a, int b){
        return this.a1.func1(a, b);
    }
}

那么,這樣的話A和B已經(jīng)沒有耦合的依賴關(guān)系了,那么調(diào)用的時(shí)候,想要減法的方法就可以調(diào)用fuc3,使用加法可以調(diào)用fuc1,此時(shí)不會(huì)和A的fuc1打架或者覆蓋。

五、開閉原則ocp

開閉原則(Open Closed Principle)是編程中最基礎(chǔ)、最重要的設(shè)計(jì)原則。

  1. 一個(gè)軟件實(shí)體、如類,模塊和函數(shù)應(yīng)該對擴(kuò)展開放(對提供方),對修改關(guān)閉(對使用方)。用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié);
  2. 當(dāng)軟件需要變化時(shí),盡量通過擴(kuò)展軟件實(shí)體的行為來實(shí)現(xiàn)變化,而不是通過已有代碼實(shí)現(xiàn)變化;
  3. 編程中遵循其他原則的目的就是遵循開閉原則

5.1 示例

//繪圖類,根據(jù)接受的shape不同來繪制圖形,【使用方】
class GraphicEidtor{
    public void drawShape(Shape s){
        if(s.type == 1) drawRec(s);
        if (s.type == 2) drawCir(s);
    }
    public void drawRec(Shape r){
        System.out.println("矩形");
    }
    public void drawCir(Shape c){
        System.out.println("圓形");
    }
}
//基類
class Shape{
    int type;
}
//【提供方】
class Rectangle extends Shape{
    Rectangle(){
        super.type = 1;
    }
}
class Circle extends Shape{
    Circle(){
        super.type = 2;
    }
}

調(diào)用的時(shí)候,給draw方法傳入不同的參數(shù),會(huì)根據(jù)類型畫出不同的圖形,我們調(diào)用一下:

GraphicEidtor graphicEidtor = new GraphicEidtor();
graphicEidtor.drawShape(new Rectangle());
graphicEidtor.drawShape(new Circle());

這種寫法還是比較好理解的,但是這種寫法很不好。

那么,這種寫法的問題在哪里呢?

違反了設(shè)計(jì)模式的 OCP 開閉原則,也就是不滿足對擴(kuò)展開放,對修改關(guān)閉。這個(gè)原則希望當(dāng)我們給類增加新功能的時(shí)候,盡量不修改代碼,或者盡可能少修改代碼。

比如我們想加一個(gè)圖形種類,那就要將這個(gè)圖形多寫一個(gè)類作為【提供方】,在畫圖類【使用方】里增加一個(gè)對應(yīng)shape類型的調(diào)用,再增加一個(gè)方法。這就是違背原則了。

5.2 改進(jìn)

思路就是:創(chuàng)建shape作為抽象類,提供一個(gè)抽線的draw方法,那么子類實(shí)現(xiàn)的時(shí)候去實(shí)現(xiàn)draw方法,這樣的話,【使用方】就不用修改代碼,滿足了開閉原則。

(其實(shí)說白了就是面向?qū)ο蟮囊馑迹@個(gè)對象自己本身需要向外提供方法,而不是讓使用方去提供方法,同時(shí),前面四個(gè)原則里也多多多少少有用到這個(gè)思路,就是讓使用方不要改動(dòng))

class GraphicEidtor1{
    public void drawShape(Shape1 s){
        s.draw();
    }
}
abstract class Shape1{
    int type;
    public abstract void draw();
}
class Rectangle1 extends Shape1{
    Rectangle1(){
        super.type = 1;
    }
    public void draw(){
        System.out.println("矩形");
    }
}
class Circle1 extends Shape1{
    Circle1(){
        super.type = 2;
    }
    public void draw(){
        System.out.println("圓形");
    }
}

這樣的話,調(diào)用的寫法是沒有任何改變的。

但是呢,如果我們要加一個(gè)新的形狀,那么就讓他自己去實(shí)現(xiàn)方法就可以了,對于【使用方】GraphicEidtor 是修改關(guān)閉的。

六、迪米特法則

Demeter Principle 迪米特法則,又叫最少知道原則

即一個(gè)類對自己依賴的類知道的越少越好,也就是說,對于被依賴的類不管多么復(fù)雜,都盡量將邏輯封裝再類的內(nèi)部。對外提供public方法,不對外泄露任何信息。

迪米特法則還有個(gè)更簡單的定義:只與直接的朋友通信。

什么是直接朋友?每個(gè)對象都會(huì)和其他對象之間有耦合關(guān)系,只要兩個(gè)對象之間有耦合關(guān)系,我們就說這兩個(gè)對象之間是朋友關(guān)系,耦合的方式有很多,依賴、關(guān)聯(lián)、組合、聚合等。
其中我們稱出現(xiàn)在成員變量、方法參數(shù)、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中 的類不是直接的朋友,也就是說,陌生的類最好不要以局部變量的形式出現(xiàn)在類的內(nèi)部。

比如A類里面直接有用到一個(gè)B b,或者某個(gè)方法有參數(shù)fuc1(B b);或者返回值類型是B,那么這叫做B以直接朋友的形式出現(xiàn)在了A里面。

比如A類里面有個(gè)方法 fuc1,fuc1用到一個(gè) B b = new B();這樣的局部變量形式讓A里面出現(xiàn)了B,這就是陌生的類,而不是直接朋友。

6.1 示例

比如一個(gè)應(yīng)用實(shí)例:

有一個(gè)學(xué)校,下屬各個(gè)學(xué)院和總部,現(xiàn)在要求打印出學(xué)校總部的員工ID和學(xué)院員工的ID。
代碼略長,但是邏輯很簡單:

public class Demeter1 {
    public static void main(String[] args) {
        CollegeManager collegeManager = new CollegeManager();
        collegeManager.printAllEmp(new SchoolManager());
    }
}
//學(xué)校總員工
class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}
//學(xué)院員工
class SchoolEmployee{
    private String id;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
//管理學(xué)院員工
class SchoolManager{
    //添加學(xué)院員工
    public List<SchoolEmployee> getAllEmployee(){
        List<SchoolEmployee> list = new ArrayList<>();
        for( int i=0; i<10; i++){
            SchoolEmployee employee = new SchoolEmployee();
            employee.setId("學(xué)院員工:"+i);
            list.add(employee);
        }
        return list;
    }
}
//學(xué)校管理類
//直接朋友類有:Employee、SchoolManager,第一個(gè)作為添加方法返回值,第二個(gè)作為輸出方法的參數(shù)
//陌生類:SchoolEmployee,違背了迪米特法則
class CollegeManager{
    //添加學(xué)校員工,返回值參數(shù)Employee:直接朋友
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<>();
        for(int i=0; i<5; i++){
            Employee employee = new Employee();
            employee.setId("學(xué)??偛繂T工:"+i);
            list.add(employee);
        }
        return list;
    }
    //輸出所有員工信息
    //參數(shù)SchoolManager:直接朋友
    public void printAllEmp(SchoolManager sub){
        //SchoolEmployee:陌生朋友,局部變量的方式
        List<SchoolEmployee> list1 = sub.getAllEmployee();
        System.out.println("-----學(xué)院員工-----");
        for(SchoolEmployee e: list1){
            System.out.println(e.getId());
        }
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("-----學(xué)校員工-----");
        for(Employee e: list2){
            System.out.println(e.getId());
        }
    }
}

這里面的問題,關(guān)于直接朋友和非直接朋友已經(jīng)標(biāo)注。

按照迪米特法則,上面出現(xiàn)了 SchoolEmployee 是陌生朋友,出現(xiàn)在了 SchoolManager 類里。這是非直接朋友關(guān)系的耦合,根據(jù)迪米特法則是應(yīng)該避免的。

6.2 改進(jìn)

其實(shí)例子寫的有點(diǎn)故意,顯然出問題的那一段,學(xué)校的輸出信息部分,要輸出學(xué)院的信息,沒必要,解決起來把輸出學(xué)院員工信息的那段,放到學(xué)院員工自己的類里面就可以了。也就是遵守了前面說的 " 盡量將邏輯封裝在類的內(nèi)部 "

那么就可以把 SchoolManager 部分輸出學(xué)院信息部分改成:

//學(xué)院員工
sub.printEmployee();

然后對應(yīng)的輸出方法,寫去CollegeManager類里面,添加一個(gè)方法:

//輸出學(xué)院所有員工的信息
public void printEmployee(){
    List<SchoolEmployee> list1 = this.getAllEmployee();//不用this也行
    System.out.println("-----學(xué)院員工-----");
    for(SchoolEmployee e: list1){
        System.out.println(e.getId());
    }
}

這樣的話,邏輯在自己類內(nèi)部,提供一個(gè)public方法供外部使用。

注意:每個(gè)類之間多多少少都會(huì)有耦合,迪米特法則只是要求降低耦合關(guān)系,而不是要求完全沒有依賴關(guān)系。完全沒有,就相當(dāng)于每個(gè)對象干個(gè)啥都在自己類里寫完,也用不著直接朋友了。

七、合成復(fù)用原則

合成復(fù)用原則:盡量使用組合/聚合的方式,而不要使用繼承。

例如: A 類和 B 類,B 類想要使用 A 類的兩個(gè)方法,第一個(gè)想到的是繼承,但是這種做法耦合性很高,如果說僅僅是想要使用這兩個(gè)方法,而沒有別的根本上需要用到繼承的必要性,那么可能會(huì)帶來很多麻煩,比如還有別的類也繼承了 A ,有必要修改 A 的時(shí)候還要考慮 B 會(huì)不會(huì)收影響。

所以盡量使用的做法就是聚合或者合成:

7.1 聚合

將 A 作為一個(gè)私有變量加入到 B 里面,在 B 里面寫一個(gè) set 方法將 A 實(shí)例化,然后去調(diào)用想要的方法,這就叫做聚合。類似于我們在前面第三個(gè)原則”依賴倒轉(zhuǎn)原則“最后寫的傳遞依賴的方式的setter方法。

7.2 組合

將 A 直接實(shí)例化在 B 里面,那么 B 創(chuàng)建的時(shí)候,A 就已經(jīng)有了一個(gè)實(shí)例化的對象,然后調(diào)用方法,和前面的里氏替換法則后面的做法是一樣的。

八、總結(jié)

其實(shí)上面的七個(gè)原則很多地方的解決方案和沖突都是有重復(fù)的部分,實(shí)際上我們總結(jié)一下核心思想就是:

  1. 盡量把需要變化的部分獨(dú)立出來,不要和不變的代碼寫在一起;
  2. 如果對別的部分影響大,盡量寫成接口
  3. 為了松耦合努力。(可以說七個(gè)原則這個(gè)理論本身就是非常松耦合……)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容