大話設計模式讀書筆記(橋接模式)
人物:大鳥,小菜
事件:大鳥玩魂鬥羅手機遊戲,小菜也想玩,但因為這款手機遊戲只能適配大鳥的手機,卻不能適配小菜的手機,小菜抱怨說如果遊戲軟體能夠統一適配就好了,大鳥笑著給小菜講解了橋接模式
橋接模式:
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 { @Overridepublic 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功能,就增加一個類就行,如果增加手機品牌,也只是增加一個類就行,不會影響其他類,這個模式其實叫做橋接模式。橋接模式也就是將抽象部分與它的實現部分分離,使它們都可以獨立地變化。