桥接模式 (Bridge)
手握设计模式宝典 - 桥接模式 (Bridge)
Design-Pattern - Structural - Bridge
- Title: 《手握设计模式宝典》 之 桥接模式
Bridge
- Tag:
Design-Pattern
、Structural
、Bridge
、桥接模式
、设计模式
- Author: Tacks
- Create-Date: 2023-08-22
- Update-Date: 2023-08-22
大纲
0、REF
- @refactoringguru - 桥接模式
- @PHP设计模式全集 - 桥梁模式(Bridge)
- @php设计模式学习 - 2.6. 桥梁(桥接)模式(Bridge)
- @runoob - 桥接模式
- @long2ge - 极简设计模式-桥接模式
- @Github - majidkarimizadeh/bridge-design-pattern-sample
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 等方式
- 桥接模式和适配器,从代码上看,感觉很像?
- 桥接模式是事前准备;前期规划阶段拆分出来抽象部分和实现部分,将实现部分进行独立开发互不影响。
- 适配器模式是事后补救;后期引入的组件与旧组件不兼容,想要直接替换之前的客户端调用的组件,就需要提供适配器来复用代码,满足既要又要。