概述:
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。意图:
将抽象部分与实现部分分离,使它们都可以独立的变化。——《设计模式》GOF
论述:
在了解何谓桥接模式之前,我们先来看一下经典的面向对象设计原则之一:DIP原则。所谓DIP原则,即Dependency Inversion Principle,用中文解释就是“依赖倒转原则”。
在传统的面向过程的设计中,高层次模块往往依赖于低层次模块,抽象模块往往依赖于具体模块。然而,这种设计带有很大的弊端,因为处于低层次或具体模块的通常是一些复杂的算法或逻辑处理,这个部分的代码是不稳定的、易变的;而处于高层次或抽象模块的通常是一些宏观性的策略和模型,这个部分的代码是稳定的、不易改变的。
如果让一个系统中稳定的部分去依赖那些不稳定的部分,那么当那些不稳定的部分发生改变时,将不得不去改变那些稳定的部分,从而增加了整个系统扩展的复杂度。联想到实际生活中,显然让一个公司的高层去依赖底层即由底层来指导高层的做法是非常荒谬的,虽然在开发项目的时候,程序员被反复告知:应当为企业级的应用构建一个稳定的不能轻易改变的地基,但是,一方面,这个要求实现起来,需要较大的难度,另一方面,代码所处的位置(比如是底层或者是高层),并不是衡量代码变更频率的唯一标志?
DIP原则就是要改变这种依赖关系,将依赖的顺序倒转过来。简而言之,DIP原则就是让具体去依赖于抽象,即要针对接口编成,不要针对实现编程。
所谓针对接口编程,即使用Java接口或抽象类进行变量的声明、参数的声明、方法的返回类型声明。我们即将论述的桥接模式就是基于DIP原则设计的。
其实,DIP原则和程序员被反复告诫的“应当依赖于一个稳定的地基”这个原则并不冲突,它们的原则都是,“让变动的代码依赖于相对稳定的类”,只不过,Java里的接口(Interface)比起项目的底层代码,具有更高的稳固性。
![](https://i-blog.csdnimg.cn/blog_migrate/56b764b02615f7208152a0ff7eb2fbc9.gif)
抽象化(Abstraction)角色:抽象化给出定义,并保存一个对实现化对象的引用。
修正抽象化(refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
实现化(Implementor)角色:给出实现化角色的接口,但不给出具体的实现。
具体实现化(Concrete Implementor)角色:给出实现化角色的具体实现。
抽象化角色可以通过向不同的实现化对象委派,来达到动态转换自己的功能的目的。可以动态地增加修正抽象化角色和具体实现化角色,而不用修改现有的代码,符合“开-闭”原则。
●Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
●RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
●Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
●ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
代码示例:
public abstract class F1 {
public ITire tire;
public void setTire(ITire tire){
this.tire = tire;
ForNewTires();
}
public abstract void ForNewTires();
}
Implementor (实现类接口)
public interface ITire {
void function();
}
RefinedAbstraction (扩充抽象类)
/**
*10年用车
*/
public class F10 extends F1 {
@Override
public void ForNewTires() {
// TODO Auto-generated method stub
System.out.println("这是法拉利赛车");
tire.function();
}
}
雷诺用车:
/**
*雷诺 R25
*/
public class RenaultR25 extends F1 {
@Override
public void ForNewTires() {
// TODO Auto-generated method stub
System.out.println("这是雷诺赛车");
tire.function();
}
}
ConcreteImplementor (具体实现类):
public class DryTyres implements ITire {
@Override
public void function() {
// TODO Auto-generated method stub
System.out.println("天气晴朗,使用干胎");
}
}
中性胎:
public class IntermediateTyre implements ITire{
@Override
public void function() {
// TODO Auto-generated method stub
System.out.println("刚刚开始下雨,换中性胎");
}
}
雨胎:
public class RainTires implements ITire{
@Override
public void function() {
// TODO Auto-generated method stub
System.out.println("雨胎,适合积水较多时使用");
}
}
client:
public static void main(String[] args) {
// TODO Auto-generated method stub
//准备好轮胎和赛车
ITire tire = new DryTyres();
ITire tire2 = new IntermediateTyre();
ITire tire3 = new RainTires();
//法拉利的F10
F10 f10 = new F10();
//雷诺的R25
RenaultR25 r25 = new RenaultR25();
System.out.println("--比赛开始天气晴朗--");
f10.setTire(tire);
r25.setTire(tire);
System.out.println("--比赛中开始下雨--");
f10.setTire(tire2);
r25.setTire(tire2);
System.out.println("--比赛中雨越下越大,出现积水--");
f10.setTire(tire3);
r25.setTire(tire3);
}
测试结果:
--比赛开始天气晴朗--
这是法拉利赛车
天气晴朗,使用干胎
这是雷诺赛车
天气晴朗,使用干胎
--比赛中开始下雨--
这是法拉利赛车
刚刚开始下雨,换中性胎
这是雷诺赛车
刚刚开始下雨,换中性胎
--比赛中雨越下越大,出现积水--
这是法拉利赛车
雨胎,适合积水较多时使用
这是雷诺赛车
雨胎,适合积水较多时使用
当然,F1里面还有其他车队,随着科技的发展,未来轮胎种类可能增多,想要组合,采用桥接模式还是比较方便。
特点:
桥接模式的主要目的是将一个对象的变化因素抽象出来,不是通过类继承的方式来满足这个因素的变化,而是通过对象组合的方式来依赖因素的抽象,这样当依赖的因素的具体实现发生变化后,而我们的具体的引用却不用发生改变,因为我们的对象是依赖于抽象的,而不是具体的实现。
而且,通过这样的依赖抽象,我们在多个对象共享这样的因素的时候,就成为可能,如果我们使用的是具体的因素的共享,当我们改变这个变化因素的时候,我们必须把使用这个因素的所有的对象,都进行相应的修改,而如果所有的引用这个变化因素的对象都依赖于抽象而不是具体的依赖呢?这也为我们的共享的提供了变化性。效果及实现要点:
- Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
- 所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同路上的不同汽车。
- Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
- Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。
适用性:
在以下的情况下应当使用桥接模式:- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
- 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
- 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
Bridge 模式是一个非常有用的模式,也非常复杂,它很好的符合了开放-封闭原则和优先使用对象,而不是继承这两个面向对象原则。
桥接模式与装饰的区别:
装饰模式:这两个模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合可以实现很多的功能组合 .
桥接模式:
桥接模式则把原来的基类的实现化细节抽象出来,在构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度上的独立变化。