1. 程式人生 > 實用技巧 >大話設計模式讀書筆記(橋接模式)

大話設計模式讀書筆記(橋接模式)

人物:大鳥,小菜

事件:大鳥玩魂鬥羅手機遊戲,小菜也想玩,但因為這款手機遊戲只能適配大鳥的手機,卻不能適配小菜的手機,小菜抱怨說如果遊戲軟體能夠統一適配就好了,大鳥笑著給小菜講解了橋接模式


橋接模式:

1.闡述了設計程式時緊耦合思路演化

2.為解決緊耦合的缺陷,引出了合成/聚合複用原則

3.由合成/聚合複用原則展開了鬆耦合實現

緊耦合的程式演化

1.用程式碼設計:一個N品牌的手機,擁有一個小遊戲

遊戲類:

@Slf4j
public class HandsetNGame {
    public void runGame() {
        log.info("執行N品牌手機遊戲");
    }
}

客戶端:

HandsetNGame game = newHandsetNGame();
game.runGame();

2.設計:一個品牌N的手機有一個小遊戲,一個品牌M的手機有一個小遊戲(因為兩個品牌都有遊戲,他們都有共同的runGame介面,所以可以抽象個父類出來)

HandsetGame類,抽象父類:

@Slf4j
public abstract class HandsetGame {
    public abstract void runGame();
}

M類手機和N類手機都繼承它:

@Slf4j
public class HandsetMGame extends HandsetGame {
    @Override
    
public void runGame() { log.info("執行M品牌手機遊戲"); } }
@Slf4j
public class HandsetNGame extends HandsetGame {
    @Override
    public void runGame() {
        log.info("執行N品牌手機遊戲");
    }
}

3.設計:M品牌手機和N品牌手機再加上都有通訊錄功能

小菜的結構圖:

程式碼實現如下:

手機品牌:

public class HandsetBrand {
    public void phoneRun() {
    }
}

手機品牌N和手機品牌M:

public class HandsetBrandN extends HandsetBrand {
}
public class HandsetBrandM extends HandsetBrand {
}

手機品牌M的遊戲和通訊錄:

@Slf4j
public class HandsetBrandMGame extends HandsetBrandM {
    @Override
    public void phoneRun(){
       log.info("執行M品牌手機遊戲");
    }
}
@Slf4j
public class HandsetBrandMAddressList extends HandsetBrandM {
    @Override
    public void phoneRun() {
        log.info("執行M品牌手機通訊錄");
    }
}

手機品牌N的遊戲和通訊錄:

@Slf4j
public class HandsetBrandNGame extends HandsetBrandN {
    @Override
    public void phoneRun() {
        log.info("執行N品牌手機遊戲");
    }
}
@Slf4j
public class HandsetBrandNAddressList extends HandsetBrandN {
    @Override
    public void phoneRun() {
        log.info("執行N品牌手機通訊錄");
    }
}

客戶端程式碼:

public class PhoneCliengt {
    public static void main(String[] args) {
        HandsetBrand ab;

        ab = new HandsetBrandMAddressList();
        ab.phoneRun();

        ab = new HandsetBrandMGame();
        ab.phoneRun();

        ab = new HandsetBrandNAddressList();
        ab.phoneRun();

        ab = new HandsetBrandNGame();
        ab.phoneRun();
    }
}

輸出結果:

執行M品牌手機通訊錄
執行M品牌手機遊戲
執行N品牌手機通訊錄
執行N品牌手機遊戲

大鳥:如果每個手機增加mp3功能

小菜:再在每個手機下增加一個子類

大鳥:如果再增加一個手機品牌

小菜:那就再增加一個手機品牌和三個子類,現在感覺有點麻煩了

大鳥:如果再增加一個功能,那不是又要增加三個子類麼

小菜:那我換一種思路,如下:

小菜思考了下:還是不行,如果要增加一個功能,還是會有很大的影響

合成/聚合複用原則

儘可能使用合成/聚合,儘量不要使用類繼承

因為物件的繼承是在編譯時就定義好了,所以執行時無法改變從父類繼承的實現,子類和父類有非常緊密的依賴關係,當需要複用子類時,當繼承下來的實現不適合解決新的問題,則父類必須重寫或被其他合適的類替換,這種依賴關係限制了靈活性,並最終限制了複用性。

1.合成/聚合結構圖

2.合成/聚合的好處:優先使用物件的合成/聚合將有助於你保持每個類被封裝,並集中在單個任務上。這樣的類和類繼承層次會保持較小的規模,並且不太可能增長成不可控制的龐然大物。

3.結合上述例子的程式碼結構圖:

小菜:手機品牌包含手機軟體,但手機軟體不是手機品牌的一部分,所以是聚合關係

鬆耦合的程式

HandsetSoft類,手機軟體:

public abstract class HandsetSoft {
    public abstract void run();
}

HandsetGame類,手機遊戲:

@Slf4j
public class HandsetGame extends HandsetSoft {
    @Override
    public void run() {
        log.info("執行手機遊戲");
    }
}

HandsetAddressList類,手機通訊錄:

@Slf4j
public class HandsetAddressList extends HandsetSoft {
    @Override
    public void run() {
        log.info("執行手機通訊錄");
    }
}

HandsetBrand類,手機品牌類:

public abstract class HandsetBrand {
    protected HandsetSoft soft;

//設定手機軟體
public void setHandsetSoft(HandsetSoft soft) { this.soft = soft; } public abstract void run(); }

品牌N,品牌M具體類:

public class HandsetBrandN extends HandsetBrand {
    @Override
    public void run() {
        soft.run();
    }
}
public class HandsetBrandM extends HandsetBrand {
    @Override
    public void run() {
        soft.run();
    }
}

客戶端呼叫:

public class PhoneClient {
    public static void main(String[] args) {
        HandsetBrand ab;
        ab = new HandsetBrandN();

        ab.setHandsetSoft(new HandsetGame());
        ab.run();

        ab.setHandsetSoft(new HandsetAddressList());
        ab.run();

        ab = new HandsetBrandM();

        ab.setHandsetSoft(new HandsetGame());
        ab.run();

        ab.setHandsetSoft(new HandsetAddressList());
        ab.run();
    }
}

大鳥:這樣如果增加mp3功能,就增加一個類就行,如果增加手機品牌,也只是增加一個類就行,不會影響其他類,這個模式其實叫做橋接模式。橋接模式也就是將抽象部分與它的實現部分分離,使它們都可以獨立地變化。