桥接(Bridge)模式

文章目录

  • 桥接(Bridge)模式
    • 1. 意图
    • 2. 别名
    • 3. 动机
    • 4. 适用性
    • 5. 结构
    • 6. 参与者
    • 7. 协作
    • 8. 效果
    • 9. 实现
    • 10. 代码示例
    • 11. 已知应用
    • 12. 相关应用
    • 13. 设计原则口袋
    • 14. 参考文献

桥接(Bridge)模式

隶属类别——对象结构型模式


1. 意图

将抽象的部分和它的实现部分分离,使他们都可以独立变化。

2. 别名

Handle/Body

3. 动机

当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口。而具体的子类则用不同方式加以实现。但是此方法有时不够灵活,继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立地进行修改,扩充和重用。

让我们考虑在一个用户界面工具箱中,一个可移植的Window抽象部分的实现。例如,这一抽象部分应该允许用户开发一些在X Window System和IBM的Presentation Manager(PM)系统中都可以使用的应用程序。运用继承机制,我们可以定义Window抽象类和它的两个子类XWindow 与PMWindow,由它们分别实现不同系统平台的Windows界面,但是继承机制有两个不足之处:

    1. 扩展Window抽象使之适用于不同种类的窗口或新的系统平台很不方便。假设有Window的一个子类IconWindow,它专门将Window抽象用于图标处理。为了使IconWindows支持两个系统平台,我们必须实现两个新类XIconWindow和PMIconWindow,更为糟糕的是,我们不得不为每一种类型的窗口都定义两个类。而为了支持第三个系统平台我们还必须为每一种窗口定义一个新的Window子类,如下图所示:

桥接(Bridge)模式_第1张图片

    1. 继承机制使得客户代码与平台相关。每当客户创建一个窗口,必须要实例化一个具体的类,这个类有特定的实现部分。例如,创建XWindow对象会将Window抽象与X Window的实现部分绑定起来,这使得客户程序依赖于X Window的实现部分。这将使得很难将客户代码移植到其他平台上去。

客户在创建窗口时应该不涉及到其具体实现部分。仅仅是窗口的实现部分依赖于应用运行的平台。这样客户代码在创建窗口时就不应涉及到特定的平台。

Bridge模式解决了以上问题的方法是,将Window抽象和它的实现部分分别放在地理的类层结构中。其中一个类层次结构针对窗口接口(Window,IconWindow,TransientWindow),另外一个独立的类层次结构针对平台相关的窗口实现部分,这个类层次结构的根类为WindowsImpl。例如XWindosImp提供一个基于X Window系统的实现,如图所示:

桥接(Bridge)模式_第2张图片

对Window子类的所有操作都是用WindowImp接口中的抽象操作实现的,这就将窗口的抽象与系统的相关实现分离开来(单一责任原则)。因此,我们1将Window与WindowImp之间的关系称为桥接,因为它在抽象类与它的实现之间起到了桥梁作用,这使他们可以独立地变化,通过委托的方式来进行方法调用,这使得调用更加灵活。

4. 适用性

以下一些情况可以使用Bridge模式:

  • 你不希望在抽象和它实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充,这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • 你想对客户完全隐藏抽象的实现部分。
  • 正如动机第一节的第一个类图所示的那样,有许多类要生成。这样一种类层次结构说明你必须将对象分解成两个部分。Rumbaugn称这种类层次结构为"嵌套的普化"(nested generalizations)
  • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并知道这一点,一个简单的例子便是Coplien的String类,在这个类中多个对象可以共享同一个字符串表示(StringRep)

5. 结构

桥接(Bridge)模式_第3张图片

6. 参与者

  • Abstraction(Window)
    • 定义抽象类的接口
    • 维护一个指向Implementor类型对象的指针
  • RefinedAbstraction(IconWindow)
    • 扩充有Abstraction定义的接口
  • Implementor(WindowImp)
    • 定义实现类的接口,该接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些操作的较高层次的操作。
  • ConcreteImplementor(XWindowImp,PMWindowImp)
    • 实现Implementor接口并定义它的具体实现

7. 协作

  • Abstraction将client的请求转发给它的Implementor对象。

8. 效果

Bridge模式有以下一些优点:

  • 1)分离接口及其实现部分 一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。

    将Abstraction和Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类是,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。

    另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abastraction和Implementor即可。

    1. 提供可扩充性 你可以独立地对Abstraction和Implementor层次结构进行扩充。
    1. 实现细节对客户透明 你可以对客户隐藏实现细节,例如共享Implementor对象以及共享的引用计数机制(如果有的话)

Bridge模式的缺点:

  • 增加了复杂度

9. 实现

使用Bridge模式需要注意以下一些问题:

    1. 仅有一个Implementor 在仅有一个实现的时候,没有必要创建一个抽象的Implememtor类。这是Bridge模式的退化情况,在Abstraction与Implementor之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响到已有的客户程序时,模式的分离机制还是非常有用的——也就是说,不必重新编译它们,仅需重新连接即可。

    在Java仅需Implementor引用设置为private。

    1. 创建正确的Implementor对象 当存在多个Implementor类的时候,你应该用何种方法,在何时何处确定创建一个哪一个Implement类呢?

    如果Abstraction知道所有的ConcreteImplementor类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。例如,如果一个collection类支持多重实现,就可以根据collection的大小决定实例化哪一个类。链表的实现可以用于较小的collection类,而hash表则可用于就较大的collection类。

    另外一个方法是首先选择一个缺省的实现,然而根据需要改变这个实现。例如,如果一个collection的大小超过了一定的阈值时,它将会切换它的实现,使之更适用于表目较多的collection。

    也可以代理给另一个对象,由它一次决定。在Window/WindowImp的例子中,我们可以引入一个factory对象,该对象的唯一职责就是封装系统平台的细节。这个对象知道如何应该为所用的平台创建何种类型的WindowImpl对象;Window仅需向它请求一个WindowImp,而它会返回正确的WindowImp对象。这种方法的优点是Abstraction类不和任何一个Implementor类直接耦合。

    1. 共享Implementor对象 在Java中如何共享Implementor对象????
    1. 采用多重继承机制 在C++ 可以使用多重继承机制将抽象接口和它的实现部分结合起来,例如,一个类可以用Public 方式继承Abstraction和private方式继承ConcreteImplementor。但是由于这种方法依赖于静态继承,它将实现部分与接口固定不变的绑定在一起,因此不可能使用多重继承的方法实现真正的Bridge模式——至少C++不行,Java更不行,Java连多重继承都无法实现。

10. 代码示例

首先创建Abstraction——Weapon.java

ppublic interface Weapon {
	void wield();
	void swing();
	void retract();
	Enchantment getEnchantment();
}

接下来是Implementor——Enchantment.java

public interface Enchantment {
	void activate();
	void apply();
	void deactivate();
}

接着是ConcreteImplementor——FlyEnchantment.java & SoulEatingEnchantment.java

FlyEnchantment.java

public class FlyingEnchantment implements Enchantment{
	
	@Override
	public void activate() {
		System.out.println("The item begins to glow faintly.");
	}
	
	@Override
	public void apply() {
		System.out.println("The item flies and strikes the enemies finally returning to owner's hand.");
	}
	
	@Override
	public void deactivate() {
		System.out.println("The item's glow fades.");
	}
}

SoulEnchantment.java

public class SoulEatingEnchantment implements Enchantment {
	
	@Override
	public void activate() {
		System.out.println("The item spreads bloodlust.");
	}
	
	@Override
	public void apply() {
		System.out.println("The item eats the soul of enemies");
	}
	
	@Override
	public void deactivate() {
		System.out.println("Bloodlust slowly disappears.");
	}
}

然后是RefinedAbstraction——Sword.java & Dragger.java

Sword.java

public class Sword implements Weapon {
	private final Enchantment enchantment;
	
	public Sword(Enchantment enchantment) {
		this.enchantment = enchantment;
	}
	
	@Override
	public void wield() {
		System.out.println("The sword is wielded");
		enchantment.activate();
	}
	
	@Override
	public void swing() {
		System.out.println("The sword is swinged");
		enchantment.apply();
	}
	
	@Override
	public void retract() {
		System.out.println("The sword is retracted");
		enchantment.deactivate();
	}
	
	@Override
	public Enchantment getEnchantment() {
		return enchantment;
	}
}

Dagger.java

public class Dagger implements Weapon {
	private final Enchantment enchantment;
	
	public Dagger(Enchantment enchantment) {
		this.enchantment = enchantment;
	}
	
	@Override
	public void wield() {
		System.out.println("The Dagger is wielded");
	    enchantment.activate();
	}
	
	@Override
	public void swing() {
		System.out.println("The Dagger is swinged");
		enchantment.apply();
	}
	
	@Override
	public void retract() {
		System.out.println("The Dagger is retracted");
		enchantment.deactivate();
	}
	
	@Override
	public Enchantment getEnchantment() {
		return enchantment;
	}
}

接下是Clent——WeaponShop.java

public class WeaponShop {
	public static void main(String[] args) {
		Weapon sword = new Sword(new SoulEatingEnchantment());
		sword.wield();
		sword.swing();
		sword.retract();
		
		System.out.println();
		
		Weapon dagger = new Dagger(new FlyingEnchantment());
		dagger.wield();
		dagger.swing();
		dagger.retract();
	}
}

以及对于的测试结果

The sword is wielded
The item spreads bloodlust.
The sword is swinged
The item eats the soul of enemies
The sword is retracted
Bloodlust slowly disappears.

The Dagger is wielded
The item begins to glow faintly.
The Dagger is swinged
The item flies and strikes the enemies finally returning to owner's hand.
The Dagger is retracted
The item's glow fades.

最后附上类的UML图(PS:由于这里Abstraction是接口,在RefinedAbstraction中持有Implementor引用)

桥接(Bridge)模式_第4张图片

11. 已知应用

NeXT’s AppKit 在图像生成和显示中使用了Bridge模式,一个图像可以有不同的表示方式,一个图像的最佳显式方式取决于显式设备的特性,特别是它的色彩数目和分辨率。如果没有AppKit的帮助,每一个应用程序中应用开发者都要确定在不同的情况下使用哪一种实现方法。

12. 相关应用

  • Abstracat Factory Abstract Factory模式可以用来创建和配置一个特定的Bridge模式。
  • Adapter Adapter模式用来帮助无关的类协同合作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

13. 设计原则口袋

  • 封装变化
  • 为交互对象的松耦合设计而努力
  • 针对接口编程,不针对实现编程
  • 多用组合,少用继承
  • 类应该对扩展开放,对修改关闭
  • 依赖抽象,不依赖具体类
  • 只和密友交交谈
  • 好莱坞原则——别找我,我会找你
  • 单一责任原则——类应该只有一个改变的理由

14. 参考文献

《设计模式:可复用面向对象软件的基础》

《HeadFirst设计模式》

你可能感兴趣的:(设计模式,Java,设计模式,桥接模式)