人物:大鸟,小菜
事件:大鸟玩魂斗罗手机游戏,小菜也想玩,但因为这款手机游戏只能适配大鸟的手机,却不能适配小菜的手机,小菜抱怨说如果游戏软件能够统一适配就好了,大鸟笑着给小菜讲解了桥接模式
桥接模式:
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功能,就增加一个类就行,如果增加手机品牌,也只是增加一个类就行,不会影响其他类,这个模式其实叫做桥接模式。桥接模式也就是将抽象部分与它的实现部分分离,使它们都可以独立地变化。