桥接(Bridge)模式,也叫作桥梁模式,英文是 Bridge Design Pattern。也是结构型设计模式之一。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”关于桥接模式,另外一种理解是:“如果一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则。

我们来看一个例子:假如我们现在有一款手机游戏,手机品牌M和手机品牌N都可以玩,这个时候代码该如何设计,我们很快可以想到,让手机游戏充当父类,品牌M和品牌N的手机游戏都去继承它,类图如下:

然后问题又来了,我们的手机都有通讯录,如果手机品牌M和品牌N都要增加通讯录功能又该如何设计,这个也不是很难,我们可以让手机品牌充当父类,手机品牌下有手机品牌M和手机品牌N,每个子类下各有通讯录和游戏子类,类图如下:

现在的结构还可以接受,那么问题来了,如果我们现在给每个品牌手机都加上音乐的功能,那么我们需要在手机品牌M和品牌M下分别加一个子类,那么问题又来了现在我们有了新的品牌手机S,他也需要增加音乐,通讯录游戏等功能,那么我们就需要增加 手机品牌S,手机品牌S通讯录,手机品牌S音乐,手机品牌S游戏等4个类,我们现在可以发现,随着功能的增加,我们如果使用继承的形式,那么我们继承的子类,就会越来越多,这个时候程序就太复杂了。
这个时候就用到我们今天的主角桥接模式了。桥接模式的定义:是将抽象和实现解偶,让它们可以独立变化。
我们先来看一下桥接模式的结构图:

桥接模式结构

  1. 抽象角色 (Abstraction) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。
  2. 实现角色 (Implementation) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。
    抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。
  3. 具体实现角色 (Concrete Implementations) 中包括特定于平台的代码。
  4. 精确抽象角色 (Refined Abstraction) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。
  5. 通常情况下, 客户端 (Client) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。

回到我们这个例子 ,我们的手机游戏,通讯录音乐等其实都是手机里的软件,我们可以将手机品牌和手机软件作为两个抽象类让不同的品牌和功能都分别继承它们,这样如果我们要增加新的品牌或功能就不会影响到其他类了。我们根据桥接模式画出这个例子中的类图,如下:

示例代码:
实现角色 手机品牌 HandsetSoft.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 实现角色
 * 手机软件
 * Class HandsetSoft
 * @package DesignPatterns\Bridge
 */
abstract class HandsetSoft
{
    abstract public function run();
}

具体实现角色 手机游戏 HandsetGame.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 具体实现角色
 * 手机游戏
 * Class HandsetGame
 * @package DesignPatterns\Bridge
 */
class HandsetGame extends HandsetSoft
{
    public function run()
    {
        return '运行手机游戏';
    }
}

具体实现角色 手机通讯录 HandsetAddressList.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 具体实现角色
 * 手机通讯录
 * Class HandsetAddressList
 * @package DesignPatterns\Bridge
 */
class HandsetAddressList extends HandsetSoft
{
    public function run()
    {
        return '运行手机通讯录';
    }
}

具体实现角色 手机音乐 HansetMusic.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 具体实现角色
 * 手机音乐
 * Class HandsetMusic
 * @package DesignPatterns\Bridge
 */
class HandsetMusic extends HandsetSoft
{
    public function run()
    {
        return '运行手机音乐';
    }
}

抽象角色 手机品牌 HandsetBrand.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 抽象角色
 * 手机品牌
 * Class HandsetBrand
 * @package DesignPatterns\Bridge
 */
abstract class HandsetBrand
{
    protected $soft;

    /**
     * 手机品牌需要关注手机软件
     * @param HandsetSoft $soft
     */
    public function __construct(HandsetSoft $soft)
    {
        $this->soft = $soft;
    }
    abstract public function run();
}

精确抽象角色 手机品牌M HandsetBrandM.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 精确抽象角色
 * 手机品牌m
 * Class HandsetBrandM
 * @package DesignPatterns\Bridge
 */
class HandsetBrandM extends HandsetBrand
{
    public function run()
    {
        echo '手机品牌M'.$this->soft->run()."<br>";
    }
}

精确抽象角色 手机品牌N HandsetBrandN.php

<?php
namespace DesignPatterns\Bridge;
/**
 * 精确抽象角色
 * 手机品牌N
 * Class HandsetBrandN
 * @package DesignPatterns\Bridge
 */
class HandsetBrandN extends HandsetBrand
{
    public function run()
    {
        echo '手机品牌N'.$this->soft->run()."<br>";
    }
}

运行Client.php

<?php
namespace DesignPatterns\Bridge;
require __DIR__.'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        //手机游戏
        $HandsetGame = new HandsetGame();
        //手机通讯录
        $HandsetAddressList = new HandsetAddressList();
        //手机音乐
        $HandsetMusic = new HandsetMusic();
        
        
        //运行手机品牌M游戏
        $HandsetBrandM = new HandsetBrandM($HandsetGame);
        $HandsetBrandM->run();
        //运行手机品牌M通讯录
        $HandsetBrandM = new HandsetBrandM($HandsetAddressList);
        $HandsetBrandM->run();
        //运行手机品牌M音乐
        $HandsetBrandM = new HandsetBrandM($HandsetMusic);
        $HandsetBrandM->run();
        echo '<br>';
        
        
        //运行手机品牌N游戏
        $HandsetBrandN = new HandsetBrandN($HandsetGame);
        $HandsetBrandN->run();
        //运行手机品牌N通讯录
        $HandsetBrandN = new HandsetBrandN($HandsetAddressList);
        $HandsetBrandN->run();
        //运行手机品牌N音乐
        $HandsetBrandN = new HandsetBrandN($HandsetMusic);
        $HandsetBrandN->run();



    }
}
$client = new Client();
$client->run();

运行结果:

总结

桥梁模式实现是比较简单的,但原理理解起来却是不太容易。弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,如本例中的手机品牌。它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”,如本例中的手机软件。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。手机品牌的所有操作最终都交给手机软啊斤来实现。

github示例:https://github.com/yangpanyao/design-patterns/tree/master/Bridge
参考书籍: 大话设计模式

Last modification:June 18th, 2020 at 05:00 pm