设计模式-桥接模式在Java中的使用示例

场景

桥接模式情境引入

假如我们需要大中小3种型号的画笔,能够绘制12种不同的颜色,如果使用蜡笔,需要准备3×12 = 36支,

但如果使用毛笔的话,只需要提供3种型号的毛笔,外加12个颜料盒即可,涉及到的对象个数仅为 3 + 12= 15,

远小于36,却能实现与36支蜡笔同样的功能。如果增加一种新型号的画笔,并且也需要具有12种颜色,

对应的蜡笔需增加12支,而毛笔只需增加一支。为什么会这样呢?通过分析我们可以得知:在蜡笔中,

颜色和型号两个不同的变化维度(即两个不同的变化原因)融合在一起,无论是对颜色进行扩展还是对型号进行

扩展都势必会影响另一个维度;但在毛笔中,颜色和型号实现了分离,增加新的颜色或者型号对另一方都没有任何影响。

如果使用软件工程中的术语,我们可以认为在蜡笔中颜色和型号之间存在较强的耦合性,而毛笔很好地将二者解耦,

使用起来非常灵活,扩展也更为方便。在软件开发中,我们也提供了一种设计模式来处理与画笔类似的具有多变化维度的情况,

即桥接模式。

需求示例

欲开发跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种

格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。

扫描二维码关注公众号,回复: 15785511 查看本文章

系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,

在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。

系统需具有较好的扩展性以支持新的文件格式和操作系统如果不使用桥接模式,可能使用的基本结构如图

初始设计方案中,使用了一种多层继承结构,Image是抽象父类,而每一种类型的图像类,

如BMPImage、JPGImage等作为其直接子类,不同的图像文件格式具有不同的解析方法,可以得到不同的像素矩阵;

由于每一种图像又需要在不同的操作系统中显示,不同的操作系统在屏幕上显示像素矩阵有所差异,

因此需要为不同的图像类再提供一组在不同操作系统显示的子类,如为BMPImage提供三个子类BMPWindowsImp

BMPLinuxImp和BMPUnixImp,分别用于在Windows、Linux和Unix三个不同的操作系统下显示图像。

此方案存在的问题:

(1)由于采用了多层继承结构,导致系统中类的个数急剧增加,在各种图像的操作系统实现层提供了12个具体类,

加上各级抽象层的类,系统中类的总个数达到了17个,在该设计方案中,

具体层的类的个数 = 所支持的图像文件格式数×所支持的操作系统数。

(2)系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此无论是

增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类,例如增加一种新的图像文件格式TIF,

则需要增加3个具体类来实现该格式图像在3种不同操作系统的显示;如果增加一个新的操作系统Mac OS,

为了在该操作系统下能够显示各种类型的图像,需要增加4个具体类。这将导致系统变得非常庞大,增加运行和维护开销。

分析该系统存在两个独立变化的纬度:图像文件格式和操作系统如何将各种不同类型的图像文件解析为像素矩阵

与图像文件格式本身相关,而如何在屏幕上显示像素矩阵则仅与操作系统相关。

如何改进?

将图像文件格式(对应图像格式的解析)与操作系统(对应像素矩阵的显示)两个维度分离,

使得它们可以独立变化,增加新的图像文件格式或者操作系统时都对另一个维度不造成任何影响。

桥接模式

桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,

使两者可以独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,

并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。

桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间 的静态继承关系转换为动态的对象组合关系,

使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数

桥接模式的结构图

桥接模式角色

Abstraction(抽象类):

用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象

并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。

RefinedAbstraction(扩充抽象类):

扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,

在RefinedAbstraction中可以调用在Implementor中定义的业务方法。

Implementor(实现类接口):

定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,

Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。

Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,

还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。

ConcreteImplementor(具体实现类):

具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,

ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。

以上面的毛笔为例

对于毛笔而言,由于型号是其固有的维度,因此可以设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,

而将各种型号的毛笔作为其子类;颜色是毛笔的另一个维度,由于它与毛笔之间存在一种“设置”的关系,

因此我们可以提供一个抽象的颜色接口,而将具体的颜色作为实现该接口的子类。在此,型号可认为是毛笔的抽象部分,

而颜色是毛笔的实现部分,结构示意图如图

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

示例一:Java中使用桥接模式实现跨平台图像浏览系统

1、使用桥接模式将跨平台图像浏览系统修改如下

 

2、其中Matrix为像素矩阵类,是辅助类,模拟转换像素矩阵

/**
 * 像素矩阵类:辅助类,各种格式的文件最终都被转换为像素矩阵,不同的操作系统提供不同的方式显示像素矩阵
 */
public class Matrix {
    public Matrix() {
        System.out.println("转换像素矩阵类成功");
    }
}

3、新建抽象图像类Image作为抽象类

/**
 * 抽象图像类:抽象类
 */
abstract class Image {
    protected ImageImpl impl;
    public void setImageImpl(ImageImpl impl){
        this.impl = impl;
    }
    public abstract void parseFile(String fileName);
}

其中其引入了实现类接口ImageImpl,这个是作为抽象操作系统实现类

/**
 * 抽象操作系统实现类:实现类接口
 */
public interface ImageImpl {
    //显示像素矩阵
    public void doPaint(Matrix matrix);
}

抽象图像类中声明文件转换的接口。实现类接口中声明显示像素矩阵的方法。

4、下面实现扩充抽象类的四种种图片格式图像

JPG格式图像:

/**
 * JPG格式图像:扩充抽象类
 */
public class JPGImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析JPG文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        impl.doPaint(m);
        System.out.println(fileName+",格式为JPG。");
    }
}

PNG格式图像:

/**
 * PNG格式图像:扩充抽象类
 */
public class PNGImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析PNG文件并获得一个像素矩阵对象m
        Matrix m = new Matrix();
        impl.doPaint(m);
        System.out.println(fileName+",格式为PNG。");
    }
}

BMP格式图像:

/**
 * BMP格式图像:扩充抽象类
 */
public class BMPImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析BMP文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        impl.doPaint(m);
        System.out.println(fileName+",格式为BMP。");
    }
}

GIF格式图像:

/**
 * GIF格式图像:扩充抽象类
 */
public class GIFImage extends Image{
    @Override
    public void parseFile(String fileName) {
        //模拟解析GIF文件并获得一个像素矩阵对象m
        Matrix m = new Matrix();
        impl.doPaint(m);
        System.out.println(fileName+",格式为GIF。");
    }
}

5、然后实现三种操作系统具体实现类

Window操作系统实现类:

/**
 * Windows操作系统实现类:具体实现类
 */
public class WindowsImpl implements ImageImpl{
    @Override
    public void doPaint(Matrix matrix) {
        //调用windows系统的绘制函数绘制像素矩阵
        System.out.println("在windows操作系统中显示图像:");
    }
}

Linux操作系统实现类:

/**
 * Linux操作系统实现类:具体实现类
 */
public class LinuxImpl implements ImageImpl{
    @Override
    public void doPaint(Matrix matrix) {
        //调用linux操作系统的绘制函数绘制像素矩阵
        System.out.println("在linux操作系统中显示图像:");
    }
}

Unix操作系统实现类:

/**
 * Unix操作系统实现类:具体实现类
 */
public class UnixImpl implements ImageImpl{
    @Override
    public void doPaint(Matrix matrix) {
        //调用Unix系统的绘制函数绘制像素矩阵
        System.out.println("在Unix操作系统中显示图像:");
    }
}

6、客户端调用示例

        Image image;
        ImageImpl impl;

        //在实际使用中可以通过分析图像文件格式后缀名来确定具体的文件格式,在程序运行时获取操作系统信息来确定操作系统类型
        image = new PNGImage();
        impl = new WindowsImpl();

        image.setImageImpl(impl);
        image.parseFile("霸道");

7、总结

当增加新的图像文件格式或者操作系统时,原有系统无须做任何修改,只需增加一个对应的扩充抽象类或具体实现类即可,

系统具有较好的可扩展性

适用场景

在以下情况下可以考虑使用桥接模式:

(1)如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,

通过桥接模式可以使它们在抽象层建立一个关联关系。

(2)“抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个

抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。

(3)一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。

(4)对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

示例二:Java中使用桥接模式实现聚合支付平台

如果上面的示例比较难理解,可借助下面聚合支付的示例进行加深理解。

聚合支付需求,支付方式有微信支付、支付宝支付等方式,每种支付方式都有刷脸支付、指纹支付等支付模式,

那么支付方式x支付模式就可以得到对应的组合。

任何一种支付模式,都会经过不同程度的安全风控,所以需要在支付模式中定义安全校验接口

1、支付方式抽象类

import java.math.BigDecimal;

/**
 * 支付方式桥接抽象类,在这个类型,定义了支付方式需要实现的划账接口:transfer,以及桥接接口:IPayMode
 */
public abstract class Pay {
    protected IPayMode payMode;
    public Pay(IPayMode payMode){
        this.payMode = payMode;
    }

    public abstract String transger(String uId, BigDecimal amount);
}

2、支付模式实现类接口

/**
 * 支付模式接口
 */
public interface IPayMode {
    //风控校验
    boolean security(String uId);
}

3、支付方式两种扩充抽象类

微信支付:

import java.math.BigDecimal;

public class WxPay extends Pay{

    public WxPay(IPayMode payMode) {
        super(payMode);
    }

    @Override
    public String transger(String uId, BigDecimal amount) {
        System.out.println("模拟微信支付划账开始");
        boolean security = payMode.security(uId);
        System.out.println("模拟微信支付风控校验");
        if(!security){
            System.out.println("微信支付划账失败");
            return "0";
        }else{
            System.out.println("微信支付划账成功");
            return "1";
        }
    }
}

支付宝支付:

import java.math.BigDecimal;

public class ZfbPay extends Pay{
    public ZfbPay(IPayMode payMode) {
        super(payMode);
    }

    @Override
    public String transger(String uId, BigDecimal amount) {
        System.out.println("模拟支付宝支付划账开始");
        boolean security = payMode.security(uId);
        System.out.println("模拟支付宝支付风控校验");
        if(!security){
            System.out.println("支付宝支付划账失败");
            return "0";
        }else{
            System.out.println("支付宝支付划账成功");
            return "1";
        }
    }
}

4、支付模式具体实现类

刷脸支付:

/**
 * 刷脸支付模式
 */
public class PayFaceMode implements IPayMode{
    @Override
    public boolean security(String uId) {
        System.out.println("人脸支付,风控校验识别");
        return true;
    }
}

指纹支付:

/**
 * 指纹支付模式
 */
public class PayFingerprintMode implements IPayMode{
    @Override
    public boolean security(String uId) {
        System.out.println("指纹支付,风控校验识别");
        return true;
    }
}

5、客户端调用示例

import java.math.BigDecimal;

//客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("模拟测试场景-微信支付方式-指纹模式:");
        Pay wxPay = new WxPay(new PayFingerprintMode());
        wxPay.transger("00001",new BigDecimal(10));
        System.out.println("模拟测试场景-支付宝支付方式-刷脸模式:");
        Pay zfbPay = new ZfbPay(new PayFaceMode());
        zfbPay.transger("00002",new BigDecimal(5));
    }
}

猜你喜欢

转载自blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131716728