桥接模式

简介

Decouple an abstraction from its implementation so that the two can vary independently.
解耦抽象和实现,使得两者可以独立的变化。

桥接模式(Bridge Pattern) 也称为 桥梁模式接口(Interfce)模式柄体(Handle and Body)模式,隶属对象结构型模式。

桥接模式 类似于多重继承方案,但是多重继承方案往往违背了类得单一职责原则,其复用性比较差,桥接模式 是比多重继承更好的替代方案。

桥接模式 的核心在于 解耦抽象和实现

:此处的 抽象 并不是指 抽象类接口 这种高层概念,实现 也不是 继承接口实现抽象实现 其实指的是两种独立变化的维度。其中,抽象 包含 实现,因此,一个 抽象 类的变化可能涉及到多种维度的变化导致的。

主要解决

当一个类内部具备两种或多种变化维度时,使用 桥接模式 可以解耦这些变化的维度,使高层代码架构稳定。

优缺点

优点

  • 抽象和实现分离:这是 桥接模式 的主要特点,也是避免使用 继承 的主要原因。使用 桥接模式,解耦了 抽象实现,使得两者的变化不会对另一方产生影响,使得系统扩展性大大增强。
  • 优秀的扩展能力桥接模式 的出现就是为了解决多个独立变化的维度的耦合,其高层模块聚合关系已确定(稳定)。因此,无论是 抽象 变化还是 实现 变化,只要对其进行扩展即可,高层代码无需任何更改即可接收扩展。高层代码依赖抽象,严格遵循了 依赖倒置原则
  • 实现细节对客户透明:由于 桥接模式 在高层模块(抽象层)通过聚合关系构建了稳定的架构(封装)。因此,客户只需与高层模块交互,对 抽象实现 的细节完全不需关注。

缺点

  • 桥接模式 由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程,因此会增加系统的理解与设计难度。

使用场景

  • 一个类存在两个(或多个)独立变化的维度,且这两个维度都需要进行扩展;
  • 不希望或不适合使用继承的场景;

桥接模式 的一个常用使用场景就是为了替换 继承。我们知道,继承 拥有很多优点,比如 抽象封装多态 等,父类封装共性,子类实现特性。继承 可以很好地帮助我们实现代码复用(封装)的功能,但是同时,这也是 继承 的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明了 继承 具备 强侵入性(父类代码侵入子类),同时会导致子类臃肿······因此,在设计模式中,有一个原则为:优先使用组合/聚合的方式,而不是继承
但是,设计模式是死的,人是活的。很多时候,你分不清该使用 继承 还是 组合/聚合 或其他方式等,可以从 现实语义 进行思考,因为软件(代码)最终还是提供给现实生活中的人用的,是服务于人类社会的,软件(代码)是具备现实场景的,单你从纯代码角度无法看清问题时,现实角度可能会给你提供更加开阔的思路。

模式讲解

首先来看下 桥接模式 的通用 UML 类图:

桥接模式

从 UML 类图中,我们可以看到,桥接模式 主要包含四种角色:

  • 抽象(Abstraction):该类持有一个对 实现 角色的引用,抽象 角色中的方法需要 实现 角色来实现。抽象 角色一般为抽象类(构造函数规定子类要传入一个 实现 对象);
  • 修正抽象(RefinedAbstraction)Abstraction 的具体实现,对 Abstraction 的方法进行完善和扩展;
  • 实现(Implementor):定义 实现 维度的基本操作,提供给 Abstraction 使用。该类一般为接口或抽象类;
  • 具体实现(ConcreteImplementor)Implementor 的具体实现;

下面是 桥接模式 的通用代码:

package com.yn.design_pattern.bridge.universal;

class Client {
    public static void main(String[] args) {
        // 来一个实现化角色
        IImplementor imp = new ConcreteImplementor();
        // 来一个抽象化角色,聚合实现
        Abstraction abs = new RefinedAbstraction(imp);
        // 执行操作
        abs.operation();
    }

    // 抽象实现
    interface IImplementor {
        void operationImpl();
    }

    // 具体实现
    static class ConcreteImplementor implements IImplementor {

        @Override
        public void operationImpl() {
            System.out.println("I'm ConcreteImplementor A");
        }
    }

    // 抽象
    abstract static class Abstraction {
        protected IImplementor mImplementor;

        public Abstraction(IImplementor implementor) {
            this.mImplementor = implementor;
        }

        public void operation() {
            this.mImplementor.operationImpl();
        }
    }

    // 修正抽象
    static class RefinedAbstraction extends Abstraction {
        public RefinedAbstraction(IImplementor implementor) {
            super(implementor);
        }

        @Override
        public void operation() {
            super.operation();
            System.out.println("refined operation");
        }
    }
}

举个例子

下面给个示例,让我们能更好地理解 桥接模式
例子:我们去咖啡馆喝咖啡,一般有4种选择:大杯加糖,大杯不加糖,小杯加糖,小杯不加糖。那么怎样用代码模拟选择咖啡呢?

分析:按上面的例子,很简单,咖啡就只有4种类型,因此我们建立一个“大杯加糖类”,“大杯不加糖类”,“小杯加糖类“,”小杯不加糖类“就可以覆盖上面例子所列举的咖啡了。但是,这样子的话咖啡杯和味道(糖)就耦合在一起了,后续如果有其他形式的咖啡,我们只能采用继承修改的方式扩展,继承类之间存在冗余部分,因此并不是很好的处理方案。
我们再回顾上面的例子,一杯咖啡包含两种选择:大小杯,有无糖(味道)。也就是通过这两个维度就可以决定一杯咖啡。涉及多个独立维度,我们很清楚应该使用 桥接模式。这里有两个独立维度:大小杯,有无糖(味道)。按 桥接模式 来说,应该有两个 实现:大小杯,有无糖(味道)。但是两个 实现 有点冗余,完全可以把 “大小杯” 这个维度归并到咖啡本身属性中,从而这个例子的维度变成:咖啡大小杯,有无糖(味道)。

代码如下所示:

package com.yn.design_pattern.bridge.exam;

class Client {
    public static void main(String[] args) {
        // 选择咖啡味道:原味
        ICoffeeFlavor flavor = new PlainFlavor();
        // 选择大杯咖啡
        Coffee coffee = new LargeCoffee(flavor);
        // 选择完毕:大杯咖啡:原味
        coffee.makeCoffee();

        // 大杯咖啡:加糖
        new LargeCoffee(new SugarFlavor()).makeCoffee();
        // 小杯咖啡:原味
        new SmallCoffee(new PlainFlavor()).makeCoffee();
        // 小杯咖啡:加糖
        new SmallCoffee(new SugarFlavor()).makeCoffee();
    }

    // Implementor:有无糖
    interface ICoffeeFlavor {
        String addWhat();
    }

    // ConcreteImplementor:原味
    static class PlainFlavor implements ICoffeeFlavor {
        @Override
        public String addWhat() {
            return "原味";
        }
    }

    // ConcreteImplementor:加糖
    static class SugarFlavor implements ICoffeeFlavor {
        @Override
        public String addWhat() {
            return "加糖";
        }
    }

    // Abstraction:咖啡
    static abstract class Coffee {
        protected ICoffeeFlavor mFlavor;

        public Coffee(ICoffeeFlavor flavor) {
            this.mFlavor = flavor;
        }

        public abstract void makeCoffee();
    }

    // RefinedAbstraction:大杯咖啡
    static class LargeCoffee extends Coffee {
        public LargeCoffee(ICoffeeFlavor flavor) {
            super(flavor);
        }

        @Override
        public void makeCoffee() {
            System.out.println("大杯咖啡: " + this.mFlavor.addWhat());
        }
    }

    // RefinedAbstraction:小杯咖啡
    static class SmallCoffee extends Coffee {
        public SmallCoffee(ICoffeeFlavor flavor) {
            super(flavor);
        }

        @Override
        public void makeCoffee() {
            System.out.println("小杯咖啡:" + this.mFlavor.addWhat());
        }
    }
}

上面这个例子,我们采用 桥接模式 解耦了 “咖啡大小杯” 和 “有无糖(味道)” 这两个独立变化的维度。后续如果咖啡馆提供更多的服务,比如中杯咖啡,那么直接新建一个中杯类继承Coffee即可;如果是咖啡味道更改,比如可以加牛奶,蜂蜜等,那么同样只需新建一个类实现ICoffeeFlavor即可。

通过上面的例子,我们能很好地感知到 桥接模式 遵循了设计模式 里氏替换原则依赖倒置原则,最终实现了 开闭原则,对修改关闭,对扩展开放。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 发送提示消息## 考虑这样一个实际的业务功能:发送提示消息。基本上所有带业务流程处理的系统...
    七寸知架构阅读 4,996评论 5 63
  • 介绍 桥接模式(Bridge Pattern) 也称为桥梁模式,是结构型设计模式之一。桥接模式的作用就是连接 "两...
    任教主来也阅读 321评论 0 1
  • 1.初识桥接模式 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 Abstraction:抽象部分的接口。...
    王侦阅读 906评论 0 7
  • 在正式介绍桥接模式之前,我先跟大家谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如我们需要大中小3种型号的画笔,能够...
    justCode_阅读 1,766评论 0 7
  • 【学习难度:★★★☆☆,使用频率:★★★☆☆】直接出处:桥接模式梳理和学习:https://github.com/...
    BruceOuyang阅读 895评论 0 2