《一天一模式》— 桥接模式

孤者浪人 提交于 2020-04-06 18:14:05

一、桥接模式的概念

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

听懂了这句话就不用往下看了,说明你会了。

听不懂我觉得也正常,如果用一句话能学会就没人看书了。像我这种笨人,都是学会了一个模式,然后往它的定义上套。

二、什么时候使用桥接模式

上面的概念中说到,抽象部分与它的实现(功能)部分分离,使它们都可以独立地变化。看下面这句话:

    类的层级结构只有一层,功能层次结构实现层级结构是混杂在一个层级结构中时,你可以使用桥接模式。 —— 《图解设计模式》

    在说之前先说三个概念:

    • 层的层级结构层数
    • 功能层次结构
    • 实现层级结构

    可能名词比较陌生,我刚看的时候觉得好高大上啊,没听过的技术名词都觉得高大上。

    但这三个名词用白话来描述就特别简单。

    2.1类的层级结构的层数

    用一句话就能说明白。

    类的层级结构就是类与子类之间的继承的层数。

    这个图中类的层级结构的层数是:1

    这个图中类的层级结构的层数是:2

    类的层级结构的层数不能太深。

    2.2 功能层次结构

    类的功能层次结构,用一句话说不完,得两句话。

    首先两个类有继承关系。

    父类具有一些基本功能,在子类中添加了新的功能,这就叫功能层次结构。

    还是不太好理解,我当时是这么理解的,子类继承了父类的方法之外,还有自己的方法,这种层次结构叫功能层次结构。例如下面的图:

    动物定义了呼吸方法,然后狗继承了动物,狗还加了一个犬吠方法,这种层次结构就叫功能层次结构。

    2.3 实现层次结构

    类的实现层次结构,也得用两句话说明。

    首先两个类也有继承关系。

    父类通过抽象的方式来定义方法。然后子类通过实现父类的抽象方法来完成这个方法的具体实现。

    简单说就是一个类实现了父类的抽象方法。

    动物定义了一个移动的抽象方法,狗实现了这个抽象方法,这种层次结构就叫实现层次结构。

    2.4 总结

    类的层级结构只有一层,功能层次结构与实现层级结构混杂在一个层级结构中,这样很容易让类的设计变得复杂,也难以透彻地理解类的层次结构。因为自己也难以确定究竟在类的哪一个层次结构去增加新需求。

    这个时候,可以尝试使用桥接模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化。

    三、怎么使用桥接模式

    简单说,就是怎么做抽象部分与它的实现部分分离。

    下面会以一个场景来说明,主要是两件事:

    1. 这个场景下不使用桥接模式会带来什么弊端;
    2. 怎么使用桥接模式来解决这个弊端;

    先看来图来说明:

    类图以奔驰车为例做了一个模型,奔驰的车有不同的车系,每个车系都可以远程开门,而车系下的各个车的协议解析都不同。所以在车系中定义了远程开关车门的抽象方法。由各个开门协议对象去实现自己的协议解析。

    这种方法目前看没有什么问题,但是设计原则也好,设计模式也好,或者说设计工作,一个很重要任务就是要考虑到未来的需求变更。

    那么以这个图作为假设,需求变化成还有Benz的B、D、E系列的车,每个下面有2个具体车型,那么我们就要加入3个抽象类,6个实现类,就好比下面的类图:

    3.1 特定场景下不使用桥接模式带来的弊端

    类爆炸

    首先,上面的场景,从类的实现层次结构来讲,每次实现的增加都成倍的增加类,最后会出现类爆炸的场面,就如上图所示。

    相同的例子还有很多,比如有用画国画举例的,画国画需要毛笔,要用到小号、中号、大号的毛笔,另外还需要水彩(12颜色)。组合起来是:颜色数量 * 毛笔型号数量 = 12 * 3 = 36,需要36(红色小号、红色中号、红色大号,绿色小号 …… 黑色大号)个类。如果毛笔的型号需要5种,颜色需要24中,那么需要120个类。

    难扩展

    其次,上面的场景,想要实现一些新的功能,难以决定加到哪一层级。

    例如:某些车要加一些新的功能,比如全部开门、前排开门、后排开门等……难以确定要加到哪一层,比如要在C系列加一个全部开门,但C400没有这个功能,而C200、C300、C500有,如果你在父类加入那么C400也会有这个功能,如果在C200、C300、C500上加入,那代码的重复率又太高。

    再例如:想要加入开窗功能,那么又要创建额外超多的开窗协议实现类。

    3.2 如何使用桥接模式解决这个问题

    经过桥接模式重构,得到上面的类图,将协议与车型分开,通过聚合的方式将车型与协议联接在一起,起到的重点是,分开后更容易扩展。当要增加新功能时,在类的功能层次结构(左侧,车型侧)增加即可,不必对类的实现层次结构(右侧,协议侧)进行修改。反之亦然。

    上面的例子中,如果新增了A系列车的具体车型,不需要增加协议类,如果增加了开门协议,也不需要增加车型类(B型车,C型车……同A型车)。

    上一小节说到的国画需求也是一样,左侧可以是毛笔和3中型号的毛笔类,右侧是水彩和12种颜色的水彩类,加起来是15个类,添加毛笔型号不需要增加颜色一侧的类,添加颜色也不需要增加毛笔一次的类。

    3.3 具体代码

    测试Main方法,里面是桥接模式的使用方式

    package cc.xuepeng;
    
    /**
    输出:
    A系列开门协议解析。
    奔驰-A100开门
    A系列开门协议解析。
    奔驰-A200开门
    A系列开门协议解析。
    奔驰-A100: 第1个门-开门。
    奔驰-A100: 第2个门-开门。
    奔驰-A100: 第3个门-开门。
    奔驰-A100: 第4个门-开门。
    */
    public class Client {
    
        public static void main(String[] args) {
            BenzA benzA100 = new BenzA100(new ProtocolBenzA(), "奔驰-A100");
            BenzA benzA200 = new BenzA200(new ProtocolBenzA(), "奔驰-A200");
            benzA100.openDoor();
            benzA200.openDoor();
            ((BenzA100) benzA100).openAllDoor();
        }
    
    }

    具体代码

    // A系列奔驰车,类的功能结构层次的父类
    public class BenzA {
    
        protected Protocol protocol;
        protected String name;
    
        public BenzA(Protocol protocol, String name) {
            this.protocol = protocol;
            this.name = name;
        }
    
        public void openDoor() {
            protocol.resolveOpenDoorProtocol();
            System.out.println(name + "开门");
        }
    
    }
    
    public class BenzA100 extends BenzA {
    
        public BenzA100(Protocol protocol, String name) {
            super(protocol, name);
        }
    
        public void openAllDoor() {
            super.protocol.resolveOpenDoorProtocol();
            for (int i = 1; i <= 4; i++) {
                System.out.println(super.name + ": 第" + i + "个门-开门。");
            }
        }
    
    }
    
    public class BenzA200 extends BenzA {
    
        public BenzA200(Protocol protocol, String name) {
            super(protocol, name);
        }
    
    }
    
    // A系列车的协议解析类,类的实现层次结构的父类
    public abstract class Protocol {
    
        public abstract void resolveOpenDoorProtocol();
    
    }
    
    public class ProtocolBenzA extends Protocol {
    
        public void resolveOpenDoorProtocol() {
            System.out.println("A系列开门协议解析。");
        }
    
    }

    四、总结

    桥接模式,从我个人角度来看,要求我们设计时先从业务的维度出发去思考,考虑后续需求变更是否会使类的结构变得更复杂。

    如果业务复杂(类型的子类较多,实现功能也各有不同),并且后续的变更或者新功能会很频繁,那么就考虑使用桥接模式,将类的功能层次与类的实现层次拆分开,然后以包含(Has a)的方式建立功能层次与实现层侧的关系,即可在后续有需求变更时得到以下几点好处:

    • 代码结构更清晰;
    • 业务结构更明确;
    • 减少类的数量;

    以上就是我对桥接模式的一些理解,有不足之处请大家矫正,谢谢。

    易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
    该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!