桥接模式 (Bridge)

未匹配的标注

手握设计模式宝典 - 桥接模式 (Bridge)

Design-Pattern - Structural - Bridge

  • Title: 《手握设计模式宝典》 之 桥接模式 Bridge
  • Tag: Design-PatternStructuralBridge桥接模式设计模式
  • Author: Tacks
  • Create-Date: 2023-08-22
  • Update-Date: 2023-08-22

大纲

0、REF

1、5W1H

1.1 WHAT 什么是 桥接 模式?

桥接模式 (Bridge Design Pattern),又称为桥梁模式。是一种结构性设计模式。听名字就知道是连接的作用,一座桥连接起河的两岸,作用非同一般。

1.1.1 概念

将抽象和实现解耦,让它们可以独立变化, 来自 GOF 《设计模式》 中的定义。

乍一听,似懂非懂,不就是解耦吗,上层定义接口,然后在实现具体方法吗,彼此之间用接口来约束?

nonono,此抽象非彼抽象。

  • 抽象部分
    • 不是指代码的接口或者抽象类;抽象出来的一套类库,内部没有具体实现,依赖于实现部分;
  • 实现部分
    • 不是指代码的接口实现类;实现部分来具体处理逻辑;

听起来还是有点迷,换一种思路

一个类存在两个(或多个)独立变化的维度,通过组合的方式,让这两个(或多个)维度可以独立进行扩展。利用组合来代替继承,避免继承层级的指数级爆炸。

1.1.2 真实世界类比

  • 抽象和实现部分解耦

比如一款跨平台的应用。抽象部分:用户操作界面 GUI; 实现部分:操作系统的API;

抽象层控制程序的大致外观,然后真正工作委派给引用的实现对象,不同的实现只需要遵循相同的实现部分接口就能替换。

从而使得同一套 GUI ,可以在 MAC/PC 下运行。

  • 组合

通常在商品信息中就能体现出来,例如手机,可能有不同型号,不同的颜色,这种多个维度的,通过组合的方式来应对多维度

1.2 WHY 为什么有 桥接 模式?

项目实现中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,桥接模式就是应对这种多维度的变化

1.2.1 解决了什么问题

  • 单一职责
    • 抽象部分专注于高层逻辑
    • 实现部分处理具体细节
  • 多层继承解决
    • 抽象部分和实现部分,如果有多个维度变化的时候,继承方式将导致类的爆炸式增长,而桥接则可以利用组合的角度进行改善
  • 解耦
    • 避免抽象部分和实现部分过于耦合,使他们可以独立变化扩展
  • 可扩展性和灵活性
    • 动态组合抽象部分和实现部分,用 M+N 个类,可以实现 M*N 种组合

1.2.2 带来了什么问题

  • 增加了系统的理解与设计难度, 代码更加复杂
  • 需要正确区分项目中的 抽象部分实现部分

1.3 WHERE 在什么地方使用 桥接 模式?

  • 一个类存在多个维度的变化
    • 不同类型的消息通知系统可以作为一个维度,不同的消息发送方式可以作为另一个维度,通过桥接模式将它们连接起来

1.4 WHEN 什么时间使用 桥接 模式?

  • 可能有多个角度分类,每一种角度都可能变化

1.5 WHO 谁负责调用 桥接 模式?

客户端中的业务逻辑,最直接的是和抽象部分密切关联,调用的也是抽象部分的具体方法。

实现方式,用哪种方式,通常会采用配置文件的方式,来自动选择对应的实现类。

1.6 HOW 如何去实现 桥接 模式?

核心部分:抽象部分依赖实现部分。 抽象部分通过引用实现部分的对象来完成具体业务逻辑

大概有几种角色体现

  • 实现部分的接口 Implementor
  • 具体实现类 ConcreteImplementor
  • 抽象部分 Abstraction
    • 通常来说,抽象会引用实现部分 Implementor
  • 扩充抽象类 RefinedAbstraction

示例

interface ImplementorInterface
{
    public function handle();
}

abstract class Abstraction
{
    protected $implementor;
    public function __construct(ImplementorInterface $implementor)
    {
        $this->implementor = $implementor;
    }
    public abstract function hand();
}

class ConcreteAImplementor implements ImplementorInterface
{
    public function handle()
    {
        echo "A方案处理";
    }
}
class ConcreteBImplementor implements ImplementorInterface
{
    public function handle()
    {
        echo "B方案处理";
    }
}

class RefinedAbstraction extends Abstraction
{
    public function hand()
    {
        $this->implementor->handle();
    }
}

(new RefinedAbstraction(new ConcreteAImplementor()))->hand();
(new RefinedAbstraction(new ConcreteBImplementor()))->hand();

2、Code

举个例子,不同的通知类型,不同的消息发送方式,两种层次维度,可以独立变化,进行组合使用

2.1 实现部分接口-发送接口

// 实现
interface MsgSenderImplementorInterface
{
    public function send(string $sendto, string $text);
}

2.2 抽象部分接口-通知接口

// 抽象类
abstract class NotificationAbstraction
{
    protected $sender;
    protected $username;

    // 包含对实现接口的引用 MsgSenderImplementorInterface
    public function __construct(MsgSenderImplementorInterface $sender, string $username)
    {
        $this->sender   = $sender;
        $this->username = $username;
    }

    abstract public function notify($value);
}

2.3 具体实现部分-发送器可以随意变化不同的发送方式

// 具体实现类: 邮箱发送类
class EmailMsgSenderConcreteImplementor implements MsgSenderImplementorInterface
{
    public function send(string $sendto, string $text)
    {
        echo sprintf("[%s] %s 邮箱发送至:%s 内容:%s", date("Y-m-d H:i:s"), __CLASS__,  $sendto, $text) . PHP_EOL;
    }
}

// 具体实现类: 短信发送类
class SmsMsgSenderConcreteImplementor implements MsgSenderImplementorInterface
{
    public function send(string $sendto, string $text)
    {
        echo sprintf("[%s] %s 短信发送至:%s 内容:%s", date("Y-m-d H:i:s"), __CLASS__, $sendto,  $text) . PHP_EOL;
    }
}

// 具体实现类: 语言电话发送类
class VoiceMsgSenderConcreteImplementor implements MsgSenderImplementorInterface
{
    public function send(string $sendto, string $text)
    {
        echo sprintf("[%s] %s 语言电话至:%s 内容:%s", date("Y-m-d H:i:s"), __CLASS__,  $sendto, $text) . PHP_EOL;
    }
}

2.4 具体抽象部分-不同的消息通知器

// 扩展抽象类: 严重通知类
class SevereNotificationRefinedAbstraction extends NotificationAbstraction
{
    public function notify($value)
    {
        $this->sender->send($this->username, sprintf("【严重告警】:%s", $value));
    }
}

// 扩展抽象类: 正常通知类
class NormalNotificationRefinedAbstraction extends NotificationAbstraction
{
    public function notify($value)
    {
        $this->sender->send($this->username, sprintf("【普通信息】:%s", $value));
    }
}

// 扩展抽象类: 验证码通知类
class CaptchaNotificationRefinedAbstraction extends NotificationAbstraction
{
    public function notify($value)
    {
        $this->sender->send($this->username, sprintf("【验证码】:%s", $value));
    }
}

2.5 客户端组合使用

// 危险报警-用语音短信方式
$service = new SevereNotificationRefinedAbstraction(new VoiceMsgSenderConcreteImplementor(), "zhangsan");
$service->notify("网站报错 500 Internal Server Error");

// 正常通知-用邮箱方式发送
$service = new NormalNotificationRefinedAbstraction(new EmailMsgSenderConcreteImplementor(), "lisi");
$service->notify("上个月网站访问量 PV=100w UV=10w");

// 验证码通知-用短信方式发送,当然也可以邮箱方式发送
$service = new CaptchaNotificationRefinedAbstraction(new SmsMsgSenderConcreteImplementor(), "wangwu");
$service->notify("感谢注册,验证码为 6666, 有效期5分钟");

$service = new CaptchaNotificationRefinedAbstraction(new EmailMsgSenderConcreteImplementor(), "wangwu");
$service->notify("感谢注册,验证码为 6666, 有效期5分钟");

3、Application

Laravel 中 数据库连接 和 Eloquent模型 ,能体现出来一些 桥接模式。

[可能理解的不太准确]

3.1 实现部分-抽象接口

// \Illuminate\Database\ConnectionResolverInterface
namespace Illuminate\Database;

interface ConnectionResolverInterface
{
    /**
     * 获取一个数据库连接
     *
     * @param  string  $name
     * @return \Illuminate\Database\ConnectionInterface
     */
    public function connection($name = null);
}

3.2 抽象部分-抽象类

abstract class Model 
{
     /**
     * 连接解析器的实例
     *
     * @var \Illuminate\Database\ConnectionResolverInterface
     */
    protected static $resolver;


    /**
     * 获取模型的数据库连接
     *
     * @return \Illuminate\Database\Connection
     */
    public function getConnection()
    {
        return static::resolveConnection($this->getConnectionName());
    }

    /**
     * 利用解析器进行连接
     *
     * @param  string|null  $connection
     * @return \Illuminate\Database\Connection
     */
    public static function resolveConnection($connection = null)
    {
        return static::$resolver->connection($connection);
    }
}

3.3 实现部分-具体连接器

根据服务提供者 DatabaseServiceProvider ,来具体获取数据库连接配置

namespace Illuminate\Database;

class ConnectionResolver implements ConnectionResolverInterface
{
     /**
     * The default connection name.
     *
     * @var string
     */
    protected $default;

    /**
     * 获取数据库连接实例
     *
     * @param  string  $name
     * @return \Illuminate\Database\ConnectionInterface
     */
    public function connection($name = null)
    {
        if (is_null($name)) {
            $name = $this->getDefaultConnection();
        }

        return $this->connections[$name];
    }

    public function getDefaultConnection()
    {
        return $this->default;
    }
}
  • 看 数据库服务提供者 DatabaseServiceProvider
class DatabaseServiceProvider extends ServiceProvider
{
    // 启动引导
    public function boot()
    {
        // 连接解析器
        Model::setConnectionResolver($this->app['db']);
    }

    // 注册服务
    public function register()
    {
        $this->registerConnectionServices();
    }

    // 获取链接服务
    protected function registerConnectionServices()
    {
        // 注册数据库连接工厂
        $this->app->singleton('db.factory', function ($app) {
            return new ConnectionFactory($app);
        });

        // 注册 $this->app['db']
        $this->app->singleton('db', function ($app) {
            return new DatabaseManager($app, $app['db.factory']);
        });

        $this->app->bind('db.connection', function ($app) {
            return $app['db']->connection();
        });
    }


}

3.4 抽象部分-具体实现类

namespace App\Models;

class Article extends Model
{
}
  • 调用
Article::find(1);

4、 Summary

桥接 模式,① 利用组合方式,来应对多维度的变化。② 将抽象和实现解耦,让它们可以独立变化。

  • 桥接模式主要思想?
    • 利用组合进行解耦,将具体实现委派给其他对象,自己抽象部分面向接口编程;
  • 桥接模式是什么类型设计模式?
    • 结构型的
  • 什么情况下,会用桥接模式?
    • 能发现抽象部分、和实现部分;
    • 存在多个维度的变化,且这些变化之间可以独立进行时
    • 比如抽象数据库的 JDBC,那么具体实现可以有 MySql、PgSql 等方式
  • 桥接模式和适配器,从代码上看,感觉很像?
    • 桥接模式是事前准备;前期规划阶段拆分出来抽象部分和实现部分,将实现部分进行独立开发互不影响。
    • 适配器模式是事后补救;后期引入的组件与旧组件不兼容,想要直接替换之前的客户端调用的组件,就需要提供适配器来复用代码,满足既要又要。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~