设计模式(8)—— 桥接模式

132 阅读4分钟

1. 引出问题

需求:设计一个程序,能够显示BMP、JPG、GIF、PNG多种格式的文件,并且可以在Windows、Linux、UNIX等系统上运行。图片显示的过程是先把图片解析为像素矩阵,在不同的操作系统中调用不同的函数来绘制像素矩阵,随后将像素矩阵显示在屏幕上。并且要求系统有良好的可扩展性以支持新的文件格式和操作系统。

如图,程序使用的是一个多层继承结构

首先是各种图片格式类继承Image抽象类,以满足显示不同格式图片的需求,例如BMPImage、JPGImage等

其次,对于每种图片格式类,又分别有针对于不同格式的各个系统类,以满足图片在不同操作系统中显示的需求,例如针对于BMP文件格式的BMPWindowsImp、BMPLinuxImp、BMPUnixImp子类,它们分别实现BMP格式在Windows、Linux、UNIX系统下的显示需求

存在的问题:

  1. 现有设计方案中,除去抽象类Image,类的总数 = 文件格式数 × 操作系统数,程序中类的数量较多
  2. 系统的扩展性较差,无论是新增文件格式还是新增操作系统,都需要在多处修改或增加代码

通过分析可知,系统存在两个独立变化的维度:文件格式、操作系统

如何将图像文件解析为像素矩阵只和图像文件格式相关

如何将像素矩阵显示在屏幕上只和操作系统有关

在原有设计中,各个具体实现类,如BMPWindowsImp类将图像文件的解析和像素矩阵的显示两方面工作耦合在一起,违反了单一职责原则,故而导致了系统的复杂和扩展困难

改进思路:可以将两个维度分离,当一个维度产生变化时不对另一个维度造成影响

2. 桥接模式介绍

定义:将抽象部分与其实现部分分离,使它们都可以独立的变化。又称为柄体模式或接口模式

桥接模式中包含以下四种角色:

  1. Abstraction(抽象类)
  2. RefinedAbstraction(扩充抽象类)
  3. Implementor(实现类接口)
  4. ConcreteImplementor(具体实现类)

在使用桥接模式时,应该识别出一个类中存在的两个独立变化的维度,并将他们设计成两个独立的继承等级结构。

通常情况下,将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为抽象类层次结构(抽象部分),而将另一个维度设计为实现类层次结构(实现部分)。

以毛笔为例:

  • 毛笔的型号是其固有的维度,因此可以设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,而将各种不同型号的毛笔作为其子类
  • 毛笔所使用的颜料的颜色可以作为毛笔的另一个维度,它与毛笔之间存在一种“设置”关系,因此可以提供一个抽象的颜色接口,而将各种具体的颜色作为实现该接口的子类

在此,型号作为毛笔的抽象部分,而颜色作为毛笔的实现部分,结构示意图如下所示

3. 以桥接模式重构案例

辅助类:Maxtix(显示像素矩阵)

public class Matrix {
    //代码省略
}

抽象类:Image

public abstract class Image {
    ImageImp imageImp;

    public void setImageImp(ImageImp imageImp) {
        this.imageImp = imageImp;
    }

    public abstract void parseFile(String fileName);
}

实现类接口:ImageImp

public interface ImageImp {
    void doPaint(Matrix m);
}

具体实现类1:WindowsImp

public class WindowsImp implements ImageImp{
    @Override
    public void doPaint(Matrix m) {
        //调用windows系统的绘制函数绘制像素矩阵
        System.out.println("在Windows系统中显示图像...");
    }
}

具体实现类2:LinuxImp

public class LinuxImp implements ImageImp{
    @Override
    public void doPaint(Matrix m) {
        //调用Linux系统的绘制函数绘制像素矩阵
        System.out.println("在Linux系统中显示图像...");
    }
}

具体实现类3:UnixImp

public class UnixImp implements ImageImp{
    @Override
    public void doPaint(Matrix m) {
        //调用Unix系统的绘制函数绘制像素矩阵
        System.out.println("在Unix系统中显示图像...");
    }
}

扩充抽象类1:JPGImage

public class JPGImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析JPG文件,转化为像素矩阵
        Matrix m = new Matrix();
        imageImp.doPaint(m);
        System.out.println(fileName + ", 格式为JPG");
    }
}

扩充抽象类2:PNGImage

public class PNGImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析PNG文件,转化为像素矩阵
        Matrix m = new Matrix();
        imageImp.doPaint(m);
        System.out.println(fileName + ", 格式为PNG");
    }
}

扩充抽象类3:BMPImage

public class BMPImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析BMP文件,转化为像素矩阵
        Matrix m = new Matrix();
        imageImp.doPaint(m);
        System.out.println(fileName + ", 格式为BMP");
    }
}

扩充抽象类4:GIFImage

public class GIFImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析GIF文件,转化为像素矩阵
        Matrix m = new Matrix();
        imageImp.doPaint(m);
        System.out.println(fileName + ", 格式为GIF");
    }
}

客户端:Client

public class Client {
    public static void main(String[] args) {
        Image image = new JPGImage();
        ImageImp imageImp = new WindowsImp();

        image.setImageImp(imageImp);

        image.parseFile("小龙女");
    }
}

测试结果如下: