1 实践GoF的23种设计模式实现:桥接模式-德赢Vwin官网 网
0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

实践GoF的23种设计模式实现:桥接模式

元闰子的邀请 来源:元闰子的邀请 2024-04-14 09:30 次阅读

简介

GoF 对桥接模式(Bridge Pattern)的定义如下:

Decouple an abstraction from its implementation so that the two can vary independently.

也即,将抽象部分和实现部分进行解耦,使得它们能够各自往独立的方向变化

桥接模式解决了在模块有多种变化方向的情况下,用继承所导致的类爆炸问题。

举个例子,一个产品有形状和颜色两个特征(变化方向),其中形状分为方形和圆形,颜色分为红色和蓝色。如果采用继承的设计方案,那么就需要新增4个产品子类:方形红色、圆形红色、方形蓝色、圆形红色。如果形状总共有 m 种变化,颜色有 n 种变化,那么就需要新增 m * n 个产品子类!

现在我们使用桥接模式进行优化,将形状和颜色分别设计为抽象接口独立出来,这样需要新增 2 个形状子类:方形和圆形,以及 2 个颜色子类:红色和蓝色。同样,如果形状总共有 m 种变化,颜色有 n 种变化,总共只需要新增 m + n 个子类!

1f8ae0a2-f948-11ee-a297-92fbcf53809c.png

上述例子中,我们通过将形状和颜色抽象为一个接口,使产品不再依赖于具体的形状和颜色细节,从而达到了解耦的目的。桥接模式本质上就是面向接口编程,可以给系统带来很好的灵活性和可扩展性。如果一个对象存在多个变化的方向,而且每个变化方向都需要扩展,那么使用桥接模式进行设计那是再合适不过了。

当然,Go 语言从语言特性本身就把继承剔除,但桥接模式中分离变化、面向接口编程的思想仍然值得学习。

UML 结构

1fb4db3c-f948-11ee-a297-92fbcf53809c.png

场景上下文

在简单的分布式应用系统(示例代码工程)中,我们设计了一个 Monitor 监控系统模块,它可以看成是一个简单的 ETL 系统,负责对监控数据进行采集、处理、输出。监控数据来源于在线商场服务集群各个服务,当前通过消息队列模块 Mq 传递到监控系统,经处理后,存储到数据库模块 Db 上。

1fe1c4f8-f948-11ee-a297-92fbcf53809c.jpg

我们假设未来要上线一个不支持对接消息队列的服务、结果数据也需要存储到 ClickHouse 以供后续分析,为了应对未来多变的需求,我们有必要将监控系统设计得足够的可扩展。

于是,整个模块被设计为插件化风格的架构,Pipeline是数据处理的流水线,其中包含了Input、Filter和Output三类插件,Input负责从各类数据源中获取监控数据,Filter负责数据处理,Output负责将处理后的数据输出。

1fee1bc2-f948-11ee-a297-92fbcf53809c.png

上述设计中,我们抽象出Input、Filter和Output三类插件,它们各种往独立的方向变化,最后在Pipeline上进行灵活组合,这使用桥接模式正合适。

20109e22-f948-11ee-a297-92fbcf53809c.png

代码实现

//关键点1:明确产品的变化点,这里是input、filter和output三类插件,它们各自变化

//demo/monitor/input/input_plugin.go
packageinput

//关键点2:将产品的变化点抽象成接口,这里是input.Plugin,filter.Plugin和output.Plugin
//Plugin输入插件
typePlugininterface{
plugin.Plugin
Input()(*plugin.Event,error)
}

//关键点3:实现产品变化点的接口,这里是SocketInput,AddTimestampFilter和MemoryDbOutput
//demo/monitor/input/socket_input.go
typeSocketInputstruct{
socketnetwork.Socket
endpointnetwork.Endpoint
packetschan*network.Packet
isUninstalluint32
}

func(s*SocketInput)Input()(*plugin.Event,error){
packet,ok:=<-s.packets
    if !ok {
        return nil, plugin.ErrPluginUninstalled
    }
    event := plugin.NewEvent(packet.Payload())
    event.AddHeader("peer", packet.Src().String())
    return event, nil
}

// demo/monitor/filter/filter_plugin.go
package filter

// Plugin 过滤插件
type Plugin interface {
    plugin.Plugin
    Filter(event *plugin.Event) *plugin.Event
}

// demo/monitor/filter/add_timestamp_filter.go
// AddTimestampFilter 为MonitorRecord增加时间戳
type AddTimestampFilter struct {
}

func (a *AddTimestampFilter) Filter(event *plugin.Event) *plugin.Event {
    re, ok := event.Payload().(*model.MonitorRecord)
    if !ok {
        return event
    }
    re.Timestamp = time.Now().Unix()
    return plugin.NewEvent(re)
}


// demo/monitor/output/output_plugin.go
// Plugin 输出插件
type Plugin interface {
    plugin.Plugin
    Output(event *plugin.Event) error
}

// demo/monitor/output/memory_db_output.go
type MemoryDbOutput struct {
    db        db.Db
    tableName string
}

func (m *MemoryDbOutput) Output(event *plugin.Event) error {
    r, ok := event.Payload().(*model.MonitorRecord)
    if !ok {
    return fmt.Errorf("memory db output unknown event type %T", event.Payload())
    }
    return m.db.Insert(m.tableName, r.Id, r)
}

// 关键点4:定义产品的接口或者实现,通过组合的方式把变化点桥接起来。
// demo/monitor/pipeline/pipeline_plugin.go
// Plugin pipeline由input、filter、output三种插件组成,定义了一个数据处理流程
// 数据流向为 input ->filter->output
//如果是接口,可以通过定义Setter方法达到聚合的目的。
typePlugininterface{
plugin.Plugin
SetInput(inputinput.Plugin)
SetFilter(filterfilter.Plugin)
SetOutput(outputoutput.Plugin)
}

//如果是结构体,直接把变化点作为成员变量来达到聚合的目的。
typepipelineTemplatestruct{
inputinput.Plugin
filterfilter.Plugin
outputoutput.Plugin
isCloseuint32
runfunc()
}

func(p*pipelineTemplate)SetInput(inputinput.Plugin){
p.input=input
}

func(p*pipelineTemplate)SetFilter(filterfilter.Plugin){
p.filter=filter
}

func(p*pipelineTemplate)SetOutput(outputoutput.Plugin){
p.output=output
}

//demo/monitor/pipeline/simple_pipeline.go
//SimplePipeline简单Pipeline实现,每次运行时新启一个goroutine
typeSimplePipelinestruct{
pipelineTemplate
}

在本系统中,我们通过配置文件来灵活组合插件,利用反射来实现插件的实例化,实例化的实现使用了抽象工厂模式,详细的实现方法可参考【Go实现】实践GoF的23种设计模式:抽象工厂模式。

总结实现桥接模式的几个关键点:

明确产品的变化点,这里是 input、filter 和 output 三类插件,它们各自变化。

将产品的变化点抽象成接口,这里是input.Plugin,filter.Plugin和output.Plugin。

实现产品变化点的接口,这里是SocketInput,AddTimestampFilter和MemoryDbOutput。

定义产品的接口或者实现,通过组合的方式把变化点桥接起来。这里是pipeline.Plugin通过Setter方法将input.Plugin,filter.Plugin和output.Plugin三个抽象接口桥接了起来。后面即可实现各类 input、filter 和 output 的灵活组合了。

扩展

TiDB 中的桥接模式

TiDB是一款出色的分布式关系型数据库,它对外提供了一套插件框架,方便用户进行功能扩展。TiDB 的插件框架的设计,也运用到了桥接模式的思想。

203ccb32-f948-11ee-a297-92fbcf53809c.png

如上图所示,每个Plugin都包含Validate、OnInit、OnShutdown、OnFlush四个待用户实现的接口,它们可以按照各自的方向去变化,然后灵活组合在Plugin中。

//PluginpresentsaTiDBplugin.
typePluginstruct{
*Manifest
library*gplugin.Plugin
Pathstring
Disableduint32
StateState
}

//Manifestdescribesplugininfoandhowitcandobypluginitself.
typeManifeststruct{
Namestring
Descriptionstring
RequireVersionmap[string]uint16
Licensestring
BuildTimestring
//Validatedefinesthevalidatelogicforplugin.
//returnserrorwillstoploadpluginprocessandTiDBstartup.
Validatefunc(ctxcontext.Context,manifest*Manifest)error
//OnInitdefinestheplugininitlogic.
//itwillbecalledafterdomaininit.
//returnerrorwillstoploadpluginprocessandTiDBstartup.
OnInitfunc(ctxcontext.Context,manifest*Manifest)error
//OnShutDowndefinestheplugincleanuplogic.
//returnerrorwillwritelogandcontinueshutdown.
OnShutdownfunc(ctxcontext.Context,manifest*Manifest)error
//OnFlushdefinesflushlogicafterexecuted`flushtidbplugins`.
//itwillbecalledafterOnInit.
//returnerrorwillwritelogandcontinuewatchfollowingflush.
OnFlushfunc(ctxcontext.Context,manifest*Manifest)error
flushWatcher*flushWatcher

Versionuint16
KindKind
}

TiDB 在实现插件框架时,使用函数式编程的方式来定义 OnXXX 接口,更具有 Go 风格。

典型应用场景

从多个维度上对系统/类/结构体进行扩展,如插件化架构。

在运行时切换不同的实现,如插件化架构。

用于构建与平台无关的程序适配层。

优缺点

优点

可实现抽象不分与实现解耦,变化实现时,客户端无须修改代码,符合开闭原则。

每个分离的变化点都可以专注于自身的演进,符合单一职责原则。

缺点

过度的抽象(过度设计)会使得接口膨胀,导致系统复杂性变大,难以维护。

与其他模式的关联

桥接模式通常与抽象工厂模式搭配使用,比如,在本文例子中,可以通过抽象工厂模式对各个 Plugin 完成实例化,详情见【Go实现】实践GoF的23种设计模式:抽象工厂模式。

文章配图

可以在用Keynote画出手绘风格的配图中找到文章的绘图方法。



审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • UML
    UML
    +关注

    关注

    0

    文章

    122

    浏览量

    30858
  • 数据处理
    +关注

    关注

    0

    文章

    595

    浏览量

    28554
  • go语言
    +关注

    关注

    1

    文章

    158

    浏览量

    9047

原文标题:【Go实现】实践GoF的23种设计模式:桥接模式

文章出处:【微信号:yuanrunzi,微信公众号:元闰子的邀请】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    关于模式遇到的问题

    VMware使用模式,已经手动设置ip地址和主机ip同一网段,但是ifconfig显示的ip地址却和vmnet8(NAT)同一网段?怎么解决?
    发表于 10-16 15:06

    CentOS静态IP配置(模式

    [2018-08-26]-[CentOS]CentOS静态IP配置(模式
    发表于 05-12 08:27

    23基本的设计模式总结

    一样。​提到设计模式,不得不感谢GoF(***,四人组),他们1995年出版的《设计模式》一书,第一次将设计模式提升到理论高度,并将之规范化。书中一共总结了
    发表于 03-01 06:08

    C#23设计模式【完整】

    C#23设计模式
    发表于 08-21 17:38 71次下载

    23java设计模式

    JAVA的设计模式经前人总结可以分为23 设计模式根据使用类型可以分为三: 1、创建模式
    发表于 09-23 15:17 1次下载

    实践GoF23设计模式:命令模式简介

    因此,我们需要对请求进行抽象,将上下文信息封装到请求对象里,这其实就是命令模式,而该请求对象就是 Command。
    的头像 发表于 01-13 16:36 758次阅读

    模式的目标与设计

    模式的目标是使对象的抽象部分与实现部分分离,使之可以分别独立变化,以尽量避免产生耦合。
    的头像 发表于 06-01 14:29 518次阅读
    <b class='flag-5'>桥</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>的目标与设计

    设计模式结构性:模式

    模式不是将两个不相干的类链接,而是将一个需要多维度变化的类拆分成抽象部分和实现部分,并且在抽象层对两者做组合关联,是用组合的方式来解决继承的问题。
    的头像 发表于 06-08 10:49 798次阅读
    设计<b class='flag-5'>模式</b>结构性:<b class='flag-5'>桥</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>

    远程网关模式实现同一局域网组网管理(Superlink)

    助虚拟网卡技术,先由服务器平台下发网络参数,然后将各ZP网关及下挂用户终端直接建立在同一个局域网通道之内,实现各网络节点设备可以自由
    的头像 发表于 04-26 15:32 1124次阅读
    远程网关<b class='flag-5'>桥</b><b class='flag-5'>接</b><b class='flag-5'>模式</b><b class='flag-5'>实现</b>同一局域网组网管理(Superlink)

    模式应用场景

    模式(Bridge Pattern):将抽象和实现解耦, 使得两者可以独立地变化。 另外一解释是:一个类存在两个(或多个)独立变化的
    的头像 发表于 10-09 14:30 1208次阅读
    <b class='flag-5'>桥</b><b class='flag-5'>接</b><b class='flag-5'>模式</b>应用场景

    实践GoF23设计模式:备忘录模式

    相对于代理模式、工厂模式等设计模式,备忘录模式(Memento)在我们日常开发中出镜率并不高,除了应用场景的限制之外,另一个原因,可能是备忘录模式
    的头像 发表于 11-25 09:05 541次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>:备忘录<b class='flag-5'>模式</b>

    实践GoF23设计模式:适配器模式

    适配器模式所做的就是将一个接口 Adaptee,通过适配器 Adapter 转换成 Client 所期望的另一个接口 Target 来使用,实现原理也很简单,就是 Adapter 通过实现 Target接口,并在对应的方法中调用
    的头像 发表于 12-10 14:00 503次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>:适配器<b class='flag-5'>模式</b>

    VMware虚拟机的三网络模式

    。VMware提供了三网络模式模式、NAT模式和主机
    的头像 发表于 02-04 11:17 1984次阅读

    实践GoF23设计模式:解释器模式

    解释器模式(Interpreter Pattern)应该是 GoF23 设计模式中使用频率最少的一
    的头像 发表于 04-01 11:01 687次阅读
    <b class='flag-5'>实践</b><b class='flag-5'>GoF</b>的<b class='flag-5'>23</b><b class='flag-5'>种</b>设计<b class='flag-5'>模式</b>:解释器<b class='flag-5'>模式</b>

    网络模式是什么? 网络模式和路由模式的区别

    网络模式是一网络连接方式,它可以将多个设备连接在一起,使它们可以相互通信。在网络
    的头像 发表于 05-10 13:48 4258次阅读