JAVA设计模式之桥接模式
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用桥接模式。而具体使用的方式,则是将抽象部分与他们的实现部分分离,使得它们都可以独立的变化。
手机,不同品牌的手机有着类似的功能,假设现在都具有通讯录和游戏功能。或许我们会这样设计实现这样的关系:
这种结构下如果增加一个音乐功能,每个品牌类中都会增加一个新的音乐子类。如果新增一个品牌类,则需要新增一个新的品牌类,并且品牌类下还要对应增加各个功能的子类。
又或者是这个:
这种结构下新增品牌和功能同样需要很大的改动。
上述情况出现的主要原因是:对象的继承关系在编译时就已经确定,无法在运行时改变从父类继承的实现。子类的实现与他的父类有非常紧密的关系,以至于父类实现中的任何变化必然会导致子类的变化。当你需要复用子类时,父类的实现如果不适合解决新的问题,则父类必须重写或被其他更适合的类代替。这种依赖关系限制了灵活性并最终限制了复用性。
面向对象的设计中,还有一个重要的原则:合成/聚合复用原则---优先使用对象合成/聚合,而不是继承。
聚合(Aggregation)表示一种弱的拥有关系,体现的是A对象可以包含B对象,但是B不是A对象的一部分。合成(Composition)是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
比如鸟有两只翅膀,翅膀和鸟就是部分与整体的关系,并且他们的生命周期是相同的。这就是合成关系。
鸟群中有多只鸟,每只鸟都属于一个鸟群,一个鸟群中可以拥有多个鸟,所以鸟群和鸟的关系就是聚合关系。
合成/聚合复用原则的好处是优先使用对象的合成/聚合有助于保持各个类被封装,并集中在单个任务上,这样类和类继承的层次会保持较小的规模,并且不太可能增长为不可控制的庞然大物。
所以我们现在的结构图应该是这个样子:
使用代码描述如下:
首先是软件的抽象接口:
package com.hy.bridge;
abstract class SoftWare {
public abstract void run();
}
接下来是软件的具体实现:
package com.hy.bridge;
public class Camera extends SoftWare {
@Override
public void run() {
System.out.println("照相机运行中...");
}
}
package com.hy.bridge;
public class Game extends SoftWare {
@Override
public void run() {
System.out.println("魂斗罗战斗中...");
}
}
然后是手机品牌的抽象类及具体实现类:
package com.hy.bridge;
abstract class Brand {
public abstract void run();
}
package com.hy.bridge;
public class MotoRola extends Brand {
@Override
public void run() {
System.out.println("生产了一个摩托罗拉手机");
}
}
package com.hy.bridge;
public class Nokia extends Brand {
@Override
public void run() {
System.out.println("生产了一个诺基亚手机");
}
}
接下来的是将两个维度聚合起来的抽象手机类及其实现类:
package com.hy.bridge;
public abstract class IPhone {
protected Brand brand;
protected SoftWare softWare;
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
public SoftWare getSoftWare() {
return softWare;
}
public void setSoftWare(SoftWare softWare) {
this.softWare = softWare;
}
// 具体的实现交给实现部分处理
public void run() {
softWare.run();
}
// 具体的实现交给实现部分处理
public void showBrand() {
brand.run();
}
//抽象的方法,留给继承实现
public abstract void setON();
}
package com.hy.bridge;
public class Phone extends IPhone{
@Override
public void setON() {
System.out.println("手机开机...");
}
}
最后给出测试类:
package com.hy.bridge;
public class Test {
public static void main(String[] args) {
IPhone phone = new Phone();
phone.setBrand(new Nokia());
phone.showBrand();
phone.setON();
phone.setSoftWare(new Game());
phone.run();
phone.setSoftWare(new Camera());
phone.run();
System.out.println("--------------");
phone.setBrand(new MotoRola());
phone.showBrand();
phone.setON();
phone.setSoftWare(new Game());
phone.run();
phone.setSoftWare(new Camera());
phone.run();
}
}
现在如果新增其他的功能与品牌只需要添加对应的实现而不必对其他的类做修改。
最终他的类图是这个样子:
定义中 抽象与其实现分离,并不是指抽象类和派生类分离,因为这并没有意义,实现是指抽象类和实现部分用来实现自己的对象。
抽象部分和实现部分,通常意义下,应该指的是继承体系中,接口相同而实现也相同的部分则为抽象部分,而接口相同但是实现不同的部分则为实现部分。
桥接模式的核心意图就是让抽象类和实现类各自独立实现,各自进行变化避免影响其他的实现。
或者说:实现的系统可能有多个角度分类,每一种分类都有可能变化,那么就把多种角度分离出来让它们独立变化,减少他们之间的耦合。