桥接模式
桥接模式的动机(Motivation )
➢由于某些类型的固有的实现逻辑,使得它们具有两个乃至多个变化的维度。
➢如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化, 而不引入额外的复杂度?
场景描述
手机都有通讯录和游戏功能,M品牌手机和N品牌手机都有通讯录的增删改查功能和游戏功能。可以有2种结构
- 结构1:
父类是‘手机品牌’,下有‘手机品牌M’ 和‘手机品牌N’ ,每个子类下各有‘通讯录’和‘游戏’ 子类。 - 结构2:
父类是‘手机软件’,下有‘通讯录’ 和‘游戏’ 子类,每个子类下各有‘手机品牌M’和‘手机品牌N’。
问题原因
- 用面向对象的继承理论设计,先有一个品牌,然后多个品牌就抽象出一个品牌抽象类,对于每个功能,就都继承各自的品牌,或者从手机软件的角度去分类。
- 很多情况用继承会带来麻烦:
对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。
子类的实现与它的父类有非常紧密的依赖关系,父类实现中的任何变化必然会导致子类发生变化。需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。
这种依赖关系限制了灵活性并最终限制了复用性。
脱耦
- 耦合:两个实体的行为的某种强关联。将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。
- 将两个角色之闷的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。
- 因此,桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥接模式的用意。
//手机软件
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 Main {
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();
}
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的方法执行");
}
}
class Abstraction{
protected Implementor implementor;
public void setImplementor(Implementor implementor) {
this.implementor = implementor;
}
public void Operation() {
implementor.Operation();
}
}
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
本质
- 分离抽象和实现,