GOF设计模式-对象结构型模式-桥接模式

处理多维度变化-桥接模式

      在正式介绍桥接模式之前,我先跟大家谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如 我们需要大中小3种型号的画笔,能够绘制12种不同的颜色,如果使用蜡笔,需要准备3×12 = 36支,但如果使用毛笔的话,只需要提供3种型号的毛笔,外加12个颜料盒即可,涉及到的对 象个数仅为 3 + 12 = 15,远小于36,却能实现与36支蜡笔同样的功能。如果增加一种新型号的 画笔,并且也需要具有12种颜色,对应的蜡笔需增加12支,而毛笔只需增加一支。为什么会 这样呢?通过分析我们可以得知:在蜡笔中,颜色和型号两个不同的变化维度(即两个不同 的变化原因)融合在一起,无论是对颜色进行扩展还是对型号进行扩展都势必会影响另一个 维度;但在毛笔中,颜色和型号实现了分离,增加新的颜色或者型号对另一方都没有任何影 响。如果使用软件工程中的术语,我们可以认为在蜡笔中颜色和型号之间存在较强的耦合 性,而毛笔很好地将二者解耦,使用起来非常灵活,扩展也更为方便。在软件开发中,我们 也提供了一种设计模式来处理与画笔类似的具有多变化维度的情况,即本章将要介绍的桥接 模式。

桥接模式定义:

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是 一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承, 将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同 时有效控制了系统中类的个数

桥接模式结构:

举个栗子:

软件公司欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、 PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首 先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作 系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件 格式和操作系统。 

软件公司的开发人员针对上述要求,提出了一个初始设计方案,基本结构如图:(反面教材)

 

 

 初始设计方案中,使用了一种多层继承结构,Image是抽象父类,而每一种类型的 图像类,如BMPImage、JPGImage等作为其直接子类,不同的图像文件格式具有不同的解析方 法,可以得到不同的像素矩阵;由于每一种图像又需要在不同的操作系统中显示,不同的操 作系统在屏幕上显示像素矩阵有所差异,因此需要为不同的图像类再提供一组在不同操作系 统显示的子类,如为BMPImage提供三个子类BMPWindowsImp、BMPLinuxImp和 BMPUnixImp,分别用于在Windows、Linux和Unix三个不同的操作系统下显示图像。 我们现在对该设计方案进行分析,发现存在如下两个主要问题: (1)由于采用了多层继承结构,导致系统中类的个数急剧增加,图10-1中,在各种图像的操作 系统实现层提供了12个具体类,加上各级抽象层的类,系统中类的总个数达到了17个,在该 设计方案中,具体层的类的个数 = 所支持的图像文件格式数×所支持的操作系统数。 (2)系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此 无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类,例如在图 10-1中增加一种新的图像文件格式TIF,则需要增加3个具体类来实现该格式图像在3种不同操 作系统的显示;如果增加一个新的操作系统Mac OS,为了在该操作系统下能够显示各种类型 的图像,需要增加4个具体类。这将导致系统变得非常庞大,增加运行和维护开销。 如何解决这两个问题?我们通过分析可得知,该系统存在两个独立变化的维度:图像文件格 式和操作系统:

如何将各种不同类型的图像文件解析为像素矩阵与图像文件格式本身相关,而如 何在屏幕上显示像素矩阵则仅与操作系统相关。正因为图所示结构将这两种职责集中在一 个类中,导致系统扩展麻烦,从类的设计角度分析,具体类BMPWindowsImp、BMPLinuxImp 和BMPUnixImp等违反了“单一职责原则”,因为不止一个引起它们变化的原因,它们将图像文 件解析和像素矩阵显示这两种完全不同的职责融合在一起,任意一个职责发生改变都需要修 改它们,系统扩展困难。 如何改进?我们的方案是将图像文件格式(对应图像格式的解析)与操作系统(对应像素矩 阵的显示)两个维度分离,使得它们可以独立变化,增加新的图像文件格式或者操作系统时 都对另一个维度不造成任何影响。看到这里,大家可能会问,到底如何在软件中实现将两个 维度分离呢?不用着急,本章我将为大家详细介绍一种用于处理多维度变化的设计模式—— 桥接模式。
 

完整解决方案:

Image充当抽象类,其子类JPGImage、PNGImage、BMPImage和GIFImage充当扩 充抽象类;ImageImp充当实现类接口,其子类WindowsImp、LinuxImp和UnixImp充当具体实 现类。完整代码如下所示:

 

//像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同的方式显示像
class Matrix {                        //此处代码省略
}
//抽象图像类:抽象类
abstract class Image {
    protected ImageImp imp;
    public void setImageImp(ImageImp imp) {
        this.imp = imp;
    }
    public abstract void parseFile(String fileName);
}
//抽象操作系统实现类:实现类接口
interface ImageImp {
    public void doPaint(Matrix m);
//显示像素矩阵m
}
//Windows操作系统实现类:具体实现类
class WindowsImp implements ImageImp {
    public void doPaint(Matrix m) {                                        //调用Windows系统的绘制函数绘制像素矩阵
        System.out.print("在Windows操作系统中显示图像:");
    }
}
//Linux操作系统实现类:具体实现类
class LinuxImp implements ImageImp {
    public void doPaint(Matrix m) {                                        //调用Linux系统的绘制函数绘制像素矩阵
        System.out.print("在Linux操作系统中显示图像:");
    }
}
//Unix操作系统实现类:具体实现类
class UnixImp implements ImageImp {
    public void doPaint(Matrix m) {                                        //调用Unix系统的绘制函数绘制像素矩阵
        System.out.print("在Unix操作系统中显示图像:");
    }
}
//JPG格式图像:扩充抽象类
class JPGImage extends Image {
    public void parseFile(String fileName) {                                        //模拟解析JPG文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为JPG。");
    }
}
//PNG格式图像:扩充抽象类
class PNGImage extends Image {
    public void parseFile(String fileName) {                                        //模拟解析PNG文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为PNG。");
    }
}
//BMP格式图像:扩充抽象类
class BMPImage extends Image {
    public void parseFile(String fileName) {                                        //模拟解析BMP文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为BMP。");
    }
}
//GIF格式图像:扩充抽象类
class GIFImage extends Image {
    public void parseFile(String fileName) {                                        //模拟解析GIF文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为GIF。");
    }
}

 

 桥接模式总结

桥接模式是设计Java虚拟机和实现JDBC等驱动程序的核心模式之一,应用较为广泛。在软件 开发中如果一个类或一个系统有多个变化维度时,都可以尝试使用桥接模式对其进行设计。 桥接模式为多维度变化的系统提供了一套完整的解决方案,并且降低了系统的复杂度。 1.主 要优点
桥接模式的主要优点如下:
(1)分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固 有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度 的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自 都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
(2)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”, 复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少 了子类的个数。
(3)桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原 有系统,符合“开闭原则”。
2.主要缺点
桥接模式的主要缺点如下:
(1)桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者 一开始就针对抽象层进行设计与编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限 性,如何正确识别两个独立维度也需要一定的经验积累。
3.适用场景
在以下情况下可以考虑使用桥接模式:
(1)如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静 态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2)“抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态 将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实 现化角色进行动态耦合。
(3)一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩 展。
(4)对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤 为适用。
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章