桥接模式也是23种设计模式中比较常用的模式之一,在创建型模式、结构性模式和行为型模式分类中,桥接模式归类为创建型模式。
在正式介绍桥接模式之前,先简单的看一个例子,通过例子我们再介绍引入桥接模式。
假设要设计一个跨平台的图片浏览系统,该系统可以正常显示PNG、JPG、GIF等不同格式图片,而且在不同的操作系统上面也可以正常运行。系统会首先将文件解析为不同的像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,不同的操作系统可以调用不同的方法绘制像素矩阵。
首先使用类的继承机制设计如下类图:
Image是一个公共抽象父类,每一种图片格式,如ImageJPG、ImagePNG等都作为Image的直接子类,不同的图片格式具有不同的解析方法,可以得到不同的像素矩阵。由于每一种图像又需要在不同的操作系统中显示,不同的操作系统在屏幕上显示像素矩阵有所差异,因此需要为不同的图像类再提供一组在不同操作系统显示的子类,如ImagePNG又提供了三个子类WindowsPNGImpl、LinuxPNGImpl和MacPNGImpl,分别在Windows、Linux和Mac上面显示。
采用这种方案设计主要存在如下两个问题:
通过上面两个问题的分析,我们会发现主要在两个维度上面的变化,一个是图片格式维度,另外一个是操作系统维度。
如何将各种不同类型的图像文件解析为像素矩阵与图像文件格式本身相关,而如何在屏幕上显示像素矩阵则仅与操作系统相关。而上面的设计方式将图像文件解析和像素矩阵显示这两种完全不同的职责融合在一起,任意一个职责发生改变都需要修改它们,系统扩展困难。
如果是将图像文件格式(对应图像格式的解析)与操作系统(对应像素矩阵的显示)两个维度分离,使得它们可以独立变化,增加新的图像文件格式或者操作系统时都对另一个维度不造成任何影响。那么如何在软件中实现将两个维度分离呢?这就是本文所讲的设计模式-桥接模式。
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
桥接模式结构类图如下:
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
桥接模式的主要 优点 是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要 缺点 是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。
在本文中介绍的图片浏览系统,不同图片的格式都可以解析为像素矩阵Matrix,而且在不同的操作系统显示的也是像素矩阵Matrix,实际上相当于对于像素矩阵的的解析和像素矩阵的显示这两个维度进行了区分。这里的不同图片的解析和操作系统对图片的显示的共性是像素矩阵Matrix,上文所说的将对象的共同性质抽取出来,这里所指共同性质就是像素矩阵。 在实际应用过程中想要抽取出来一个共同性质类似这里的像素矩阵,可以说是相当有挑战性的工作,而且还要能够识别出共性不同维度的独立变化,更是难上加难的一件事情。 但是一旦可以识别出不同维度的变化性质,再进行设计的时候,我们在使用的时候就类似于堆积木了,不同积木的对象组合就会实现不同的效果。
桥接模式和适配器模式用于设计的不同阶段, 桥接模式 用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用 适配器模式 。但有时候在设计初期也需要考虑适配器模式,如Android开发时系统提供的各种Adapter,还有就是那些涉及到大量第三方应用接口的情况。
还是针对上面的图片浏览系统进行设计,为了减少所需生成的子类数目,实现将操作系统和图像文件格式两个维度分离,使它们可以独立改变,我们使用桥接模式来重构跨平台图像浏览系统的设计。重新设计的类图如下,可以发现子类数目明显减少了许多,而且扩展也相当方面。
//像素矩阵 public class Matrix {} //图片抽象类 public abstract class Image { protected ImageImpl impl; public Image(ImageImpl impl) { this.impl = impl; } public abstract void parseFile(String fileName); } //抽象操作系统实现类:实现类接口 public interface ImageImpl { void draw(Matrix matrix); } //不用操作系统的实现类 public class WindowsImpl implements ImageImpl { @Override public void draw(Matrix matrix) { System.out.print("Windows Image:"); } } ... //扩充抽象类 public class ImageJPG extends Image{ public ImageJPG(ImageImpl impl) { super(impl); } @Override public void parseFile(String fileName) { impl.draw(new Matrix()); System.out.println(fileName+" 格式为JPG"); } } ... //测试代码 ImageImpl windowImpl=new WindowsImpl(); Image jpg=new ImageJPG(windowImpl); jpg.parseFile("高清图片");//Windows Image:高清图片 格式为JPG ImageImpl macImpl=new MacImpl(); Image gif=new ImageGIF(macImpl); gif.parseFile("动态图片");//Mac Image:动态图片 格式为GIF