模式动机(Motivation )
- 由于某些类型的固有的实现逻辑,使得它们具有两个乃至多个变化的维度。
- 如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化, 而不引入额外的复杂度?
问题引入
手机都有通讯录和游戏功能,M品牌手机和N品牌手机都有通讯录的增删改查功能和游戏功能。可以有2种结构
结构1:
父类是‘手机品牌’,下有‘手机品牌M’ 和‘手机品牌N’ ,每个子类下各有‘通讯录’和‘游戏’ 子类。
结构2:
父类是‘手机软件’,下有‘通讯录’ 和‘游戏’ 子类,每个子类下各有‘手机品牌M’和‘手机品牌N’。
结构1的结构图
结构2的结构图
- 问题1:如果现在需要每个品牌都增加一个音乐播放功能,如何做?
A:结构1要求在每个品牌的下面都增加一个子类;结构2要求 在手机软件下面增加一个音乐播放子类,在这个子类下为每个品牌增加音乐播放功能。这两个子类的差别不大,但 是因为品牌不同,增加功能就不太容易。 - 问题2:现在又来了一家新的手机品牌‘S’ ,它也有游戏、通 讯录、音乐播放功能,如何处理?
A:那就得再增加‘手机品牌S’类和三个下属功能子类。 这就很麻烦了,如果还需要增加‘输入法’ 功能、 ‘拍照’功能,再增加‘L品牌’、“X品牌’ 类非常困难。
问题原因
-
用面向对象的继承理论设计,先有一个品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承各自的品牌,或者从手机软件的角度去分类。
-
很多情况用继承会带来麻烦: 对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。
-
子类的实现与它的父类有非常紧密的依赖关系,父类实现中的任何变化必然会导致子类发生变化。需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。
这种依赖关系限制了灵活性并最终限制了复用性。
重点!!!!!就是要解耦
- 耦合:两个实体的行为的某种强关联。将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。
- 将两个角色之闷的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。
- 因此,桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥接模式的用意。
上面的问题,霄要用对象的职责,而不是结构来考虑问题。
- 实际上,‘游戏’、‘通讯录’、音乐播放’这些功能都是软件,让其分离与手机的耦合,就大大减少面对 新需求时改动过大的不合理情况。
- 应该有个‘手机品牌’抽象类和‘手机软件’ 抽象类,让不同的品牌和功能都分别继承于它们,这样要增加新 的品牌或新的功能就不用影响其它类了。
结构图为
手机品牌和手机软件之间的关系是手机品牌包含有手机软件,但软件并不是品牌的一部分,所以它们之间是聚合关系。
下面为相应的代码
package 桥接模式;
//手机软件
abstract class HandsetSoft{
public abstract void Run();
}
//手机品牌类
abstract class HandsetBrand{
protected HandsetSoft soft;
public HandsetSoft getSoft() {
return soft;
}
public void setSoft(HandsetSoft soft) {
this.soft = soft;
}
public abstract void Run();
}
//手机游戏
class HandsetGame extends HandsetSoft{
@Override
public void Run() {
// TODO Auto-generated method stub
System.out.println("运行手机游戏");
}
}
class HandsetAddressList extends HandsetSoft{
@Override
public void Run() {
// TODO Auto-generated method stub
System.out.println("运行手机通讯录");
}
}
//手机品牌N
class HandsetBrandN extends HandsetBrand{
@Override
public void Run() {
// TODO Auto-generated method stub
System.out.print("N Brand");
soft.Run();
}
}
//手机品牌M
class HandsetBrandM extends HandsetBrand{
@Override
public void Run() {
// TODO Auto-generated method stub
System.out.print("M Brand");
soft.Run();
}
}
public class Main1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
HandsetBrand ab;
ab=new HandsetBrandN();
ab.setSoft(new HandsetGame());
ab.Run();
ab.setSoft(new HandsetAddressList());
ab.Run();
ab=new HandsetBrandM();
ab.setSoft(new HandsetGame());
ab.Run();
ab.setSoft(new HandsetAddressList());
ab.Run();
}
}
是否能解决前面提出的扩展问题?
- 如果要增加一个功能,比如拍照功能,在手机软件部分增加手机拍照子类。类的个数增加也只是一个。不会影响其他任何类。
- 如果要增加S品牌,在手机品牌部分增加一个品牌子类就可以了。个数也是一个,不会影响其他类。
代码的实现
//手机拍照
class photo extends HandsetSoft{
public void Run() {
System.out.println("运行手机拍照");
}
}
//手机品牌S
class HandsetBrandS extends HandsetBrand{
@Override
public void Run() {
// TODO Auto-generated method stub
System.out.print("S Brand");
soft.Run();
}
}
客户端
ab=new HandsetBrandS();
ab.setSoft(new HandsetGame());
ab.Run();
ab.setSoft(new HandsetAddressList());
ab.Run();
ab.setSoft(new photo());
ab.Run();
- 这样的设计符合开放-封闭原则,不会修改原来的代码,而只是扩展类就行了。
- 合成/聚合复用原则,优先使用对象的合成或聚合,而不是类继承。
- 继承很容易造成不必要的麻烦,其本质原因是:继承是一种强耦合的结构,父类变,子类就必须要变。所以在使用,继承时,一-定要在是‘is-a’的关系时再考虑使用,而不是任何时候都去使用。
桥接模式的基本代码
桥接模式结构图
Implementor类
abstract class Implementor{
public abstract void Operation();
}
ConcreteImplementorA和ConcreteImplementorB等派生类
class ConcreteImplementorA extends Implementor{
@Override
public void Operation() {
// TODO Auto-generated method stub
System.out.println("具体实现A的方法执行");
}
}
class ConcreteImplementorB extends Implementor{
@Override
public void Operation() {
// TODO Auto-generated method stub
System.out.println("具体实现B的方法执行");
}
}
Abstraction类
class Abstraction{
protected Implementor implementor;
public void setImplementor(Implementor implementor) {
this.implementor = implementor;
}
public void Operation() {
implementor.Operation();
}
}
RefinedAbstraction类
class RefinedAbstraction extends Abstraction{
public void Operation() {
implementor.Operation();
}
}
客户端
public class BridgeMain {
public static void main(String[] args) {
// TODO Auto-generated method stub
Abstraction ab=new RefinedAbstraction();
ab.setImplementor(new ConcreteImplementorA());
ab.Operation();
ab.setImplementor(new ConcreteImplementorB());
ab.Operation();
}
}
模式优缺点
优点
- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
模式适用环境
在以下情况下可以使用桥接模式:
-
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
-
抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
-
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
-
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
-
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
最后
- 桥接模式就是将抽象部分与它的实现部分分离,其实就是实现系统可能有多角度分类,每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
- 在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。
- 使用Bridge模式和原始解决方案的根本区别在于是通过继承还是通过合成/聚合的方式去实现一个功能需求。因比面向对象分析和设计中有一个原则就是: Favor Composition OverI In heritance