定义
桥接模式:将抽象部分与它实现部分分离,使它们都可以独立地变化。
桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数
结构图
实例
Sunny软件公司欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件格式和操作系统。
Sunny软件公司的开发人员针对上述要求,提出了一个初始设计方案
初始设计方案中,使用了一种多层继承结构,Image是抽象父类,而每一种类型的图像类,如BMPImage、JPGImage等作为其直接子类,不同的图像文件格式具有不同的解析方法,可以得到不同的像素矩阵;由于每一种图像又需要在不同的操作系统中显示,不同的操作系统在屏幕上显示像素矩阵有所差异,因此需要为不同的图像类再提供一组在不同操作系统显示的子类,如为BMPImage提供三个子类BMPWindowsImp、BMPLinuxImp和BMPUnixImp,分别用于在Windows、Linux和Unix三个不同的操作系统下显示图像。
我们发现以下问题:
1.用了多层继承结构,导致系统中类的个数急剧增加。
2.系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类。
我们使用桥接模式后:
结构图变为:
编码实现:
Matrix类:
像素矩阵类:各种文件最终都是转换成像素矩阵,不同的操作系统提供不同的方式显示像素矩阵
public class Matrix {
}
ImageImp类:
public abstract class ImageImp {
//显示矩阵
public abstract void doPaint(Matrix m);
}
针对不同系统ImageImp的各个实现类:
WindowsImp :
public class WindowsImp extends ImageImp{
@Override
public void doPaint(Matrix m){
System.out.println("在windows操作系统中显示图像!");
}
}
LinuxImp :
public class LinuxImp extends ImageImp{
@Override
public void doPaint(Matrix m) {
System.out.println("在Linux操作系统中显示图像");
}
}
UnixImp :
public class UnixImp extends ImageImp{
@Override
public void doPaint(Matrix m) {
//调用Unix系统的绘制函数绘制像素矩阵
System.out.print("在Unix操作系统中显示图像:");
}
}
Image 类:
public abstract class Image {
protected ImageImp imp;
public Image(ImageImp imp){
this.imp = imp;
}
public abstract void parseFile(String fileName);
}
JPGImage类:
public class JPGImage extends Image{
public JPGImage(ImageImp imp) {
super(imp);
}
public void parseFile(String fileName) {
//模拟解析JPG文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为JPG。");
}
}
GIFImage 类:
public class GIFImage extends Image {
public GIFImage(ImageImp imp) {
super(imp);
}
@Override
public void parseFile(String fileName) {
//模拟解析GIF文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为GIF。");
}
}
BMPImage 类:
public class BMPImage extends Image {
public BMPImage(ImageImp imp) {
super(imp);
}
@Override
public void parseFile(String fileName) {
//模拟解析BMP文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为BMP。");
}
}
PNGImage 类:
public class PNGImage extends Image {
public PNGImage(ImageImp imp) {
super(imp);
}
@Override
public void parseFile(String fileName) {
//模拟解析PNG文件并获得一个像素矩阵对象m;
Matrix m = new Matrix();
imp.doPaint(m);
System.out.println(fileName + ",格式为PNG。");
}
}
客户端
public class Client {
public static void main(String[] args) {
ImageImp imp = new WindowsImp();
Image image = new JPGImage(imp);
image.parseFile("1.jpg");
}
}
其实我们最后客户端的代码可以使用反射优化。根据操作系统和文件的类型,选择实例化的子类。
和适配器模式的比较
桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。
总结
桥接模式是设计Java虚拟机和实现JDBC等驱动程序的核心模式之一,应用较为广泛。在软件开发中如果一个类或一个系统有多个变化维度时,都可以尝试使用桥接模式对其进行设计。桥接模式为多维度变化的系统提供了一套完整的解决方案,并且降低了系统的复杂度。
桥接模式的主要优点如下:
(1)分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
(2)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
(3)桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
桥接模式的主要缺点如下:
(1)桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
适用场景:
一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展,对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用