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

上一篇:【Go实现】实践GoF的23种设计模式:解释器模式

简单的分布式应用系统(示例代码工程):https://github.com/ruanrunxue/Practice-Design-Pattern–Go-Implementation

简介

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 个子类!

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

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

UML 结构

场景上下文

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

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

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

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

代码实现

// 关键点1:明确产品的变化点,这里是input、filter和output三类插件,它们各自变化// demo/monitor/input/input_plugin.go
package input// 关键点2:将产品的变化点抽象成接口,这里是input.Plugin,filter.Plugin和output.Plugin
// Plugin 输入插件
type Plugin interface {plugin.PluginInput() (*plugin.Event, error)
}// 关键点3:实现产品变化点的接口,这里是SocketInput, AddTimestampFilter和MemoryDbOutput
// demo/monitor/input/socket_input.go
type SocketInput struct {socket      network.Socketendpoint    network.Endpointpackets     chan *network.PacketisUninstall uint32
}func (s *SocketInput) Input() (*plugin.Event, error) {packet, ok := <-s.packetsif !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.PluginFilter(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.PluginOutput(event *plugin.Event) error
}// demo/monitor/output/memory_db_output.go
type MemoryDbOutput struct {db        db.DbtableName 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方法达到聚合的目的。
type Plugin interface {plugin.PluginSetInput(input input.Plugin)SetFilter(filter filter.Plugin)SetOutput(output output.Plugin)
}// 如果是结构体,直接把变化点作为成员变量来达到聚合的目的。
type pipelineTemplate struct {input   input.Pluginfilter  filter.Pluginoutput  output.PluginisClose uint32run     func()
}func (p *pipelineTemplate) SetInput(input input.Plugin) {p.input = input
}func (p *pipelineTemplate) SetFilter(filter filter.Plugin) {p.filter = filter
}func (p *pipelineTemplate) SetOutput(output output.Plugin) {p.output = output
}// demo/monitor/pipeline/simple_pipeline.go
// SimplePipeline 简单Pipeline实现,每次运行时新启一个goroutine
type SimplePipeline struct {pipelineTemplate
}

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

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

  1. 明确产品的变化点,这里是 input、filter 和 output 三类插件,它们各自变化。
  2. 将产品的变化点抽象成接口,这里是 input.Pluginfilter.Pluginoutput.Plugin
  3. 实现产品变化点的接口,这里是 SocketInput, AddTimestampFilterMemoryDbOutput
  4. 定义产品的接口或者实现,通过组合的方式把变化点桥接起来。这里是 pipeline.Plugin 通过 Setter 方法将input.Pluginfilter.Pluginoutput.Plugin 三个抽象接口桥接了起来。后面即可实现各类 input、filter 和 output 的灵活组合了。

扩展

TiDB 中的桥接模式

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

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

// Plugin presents a TiDB plugin.
type Plugin struct {*Manifestlibrary  *gplugin.PluginPath     stringDisabled uint32State    State
}// Manifest describes plugin info and how it can do by plugin itself.
type Manifest struct {Name           stringDescription    stringRequireVersion map[string]uint16License        stringBuildTime      string// Validate defines the validate logic for plugin.// returns error will stop load plugin process and TiDB startup.Validate func(ctx context.Context, manifest *Manifest) error// OnInit defines the plugin init logic.// it will be called after domain init.// return error will stop load plugin process and TiDB startup.OnInit func(ctx context.Context, manifest *Manifest) error// OnShutDown defines the plugin cleanup logic.// return error will write log and continue shutdown.OnShutdown func(ctx context.Context, manifest *Manifest) error// OnFlush defines flush logic after executed `flush tidb plugins`.// it will be called after OnInit.// return error will write log and continue watch following flush.OnFlush      func(ctx context.Context, manifest *Manifest) errorflushWatcher *flushWatcherVersion uint16Kind    Kind
}

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

典型应用场景

  • 从多个维度上对系统/类/结构体进行扩展,如插件化架构。
  • 在运行时切换不同的实现,如插件化架构。
  • 用于构建与平台无关的程序适配层。

优缺点

优点

  • 可实现抽象不分与实现解耦,变化实现时,客户端无须修改代码,符合开闭原则。
  • 每个分离的变化点都可以专注于自身的演进,符合单一职责原则。

缺点

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

与其他模式的关联

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

文章配图

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

参考

[1] 【Go实现】实践GoF的23种设计模式:SOLID原则, 元闰子

[2] Design Patterns, Chapter 4. Structural Patterns, GoF

[3] 【Go实现】实践GoF的23种设计模式:抽象工厂模式, 元闰子

[4] 桥接模式, refactoringguru.cn

更多文章请关注微信公众号:元闰子的邀请

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/812702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android13 CameraServer启动流程

代码入口 frameworks/av/camera/cameraserver 里面包含了四个文件 我们先来看看Android.bp的内容 package {// See: http://go/android-license-faq// A large-scale-change added default_applicable_licenses to import// all of the license_kinds from "frameworks_a…

Flutter第七弹 网格列表GridView

1) Flutter提供了网格列表&#xff0c;怎么设置列数&#xff1f; 2&#xff09;怎么初始化每个列表项Item&#xff1f; 一、GridView简介 Flutter也存在网格列表组建GridView&#xff0c;用于展示多行多列的列表。 1.1 GridView构建 采用GridView.count() 进行构建 1.2 Gr…

服务器代理

服务器代理 配置&#xff1a;64G内存1 3090&#xff08;24g&#xff09;1P4000&#xff08;8g&#xff09; SSH连接 工作路径&#xff1a;/home/ubuntu/workspace/python Anaconda路径&#xff1a;/home/Ubuntu 1.在工作路径下创建自己的文件夹作为workspace 2.以用户ubunbtu登…

数据采集仪:自动化监测系统的核心组件

在当代的工业自动化领域&#xff0c;数据采集仪成为了一个关键的技术工具&#xff0c;它不仅仅是简单地将电信号转化为数据信号&#xff0c;而是能够实时、有效地处理和显示各种信号&#xff0c;确保整个监测系统的稳定、高效运行。 点击输入图片描述&#xff08;最多30字&…

【微信小程序】canvas开发笔记

【微信小程序】canvasToTempFilePath:fail fail canvas is empty 看说明书 最好是先看一下官方文档点此前往 如果是canvas 2d 写canvas: this.canvas,&#xff0c;如果是旧版写canvasId: ***, 解决问题 修改对应的代码&#xff0c;如下所示&#xff0c;然后再试试运行&#x…

春招百题--堆--扩展篇--找出最小

其他类似题目&#xff1a; 373. 查找和最小的 K 对数字378. 有序矩阵中第 K 小的元素719. 找出第 K 小的数对距离786. 第 K 个最小的素数分数 2040. 两个有序数组的第 K 小乘积 2386. 找出数组的第 K 大和 215. 数组中的第K个最大元素 不纠结直接sort排序解决。 class Solut…

[大模型]Yi-6B-Chat 接入 LangChain 搭建知识库助手

Yi-6B-Chat 接入 LangChain 搭建知识库助手 环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端开始…

制作framework

参考学习地址 https://www.jianshu.com/p/a15ad98bc965 注意事项&#xff1a; 1、在自动生成的.h文件中引入头文件时&#xff0c;需要完整路径 2、编译成功后如何查看位置 实际位置在&#xff1a; /Users/apple/Library/Developer/Xcode/DerivedData/项目名称-xaskhaskhkas/…

Zookeeper集群部署

目录 1.环境部署 1.1实验环境 1.2安装前环境 2.安装Zookeeper 2.1修改Zookeeper配置配置文件 2.2 设置myid号以及启动脚本 2.3 设置脚本 2.4 加权并加入系统管理 2.5 分别启动三台机器&#xff08;192.168.247.21&#xff09; 2.6 查看三台主机状态信息 1.环境部署 1…

[java]24:集合

集合&#xff1a; 1&#xff09;可以动态保存任意多个对象&#xff0c;使用比较方便&#xff01; 2)提供了一系列方便的操作对象的方法&#xff1a;add、remove、set、get等3&#xff09;使用集合添加&#xff0c;删除新元素的示意代码-简洁了 集合的框架体系&#xff1a; Java…

Kyligence 发布企业级 AI 解决方案,Data + AI 落地迈向新阶段

4月11日&#xff0c;Kyligence 2024 数智论坛暨春季发布会成功召开。Kyligence 正式发布全新的企业级 AI 解决方案&#xff0c;基于服务金融、零售、制造、医药等行业领先客户的落地实践&#xff0c;Kyligence 为企业提供准确、可靠、智能的 AI 指标平台一站式解决方案&#x…

影响小程序SSL证书收费标准的因素有哪些?

在当今互联网时代&#xff0c;移动应用发展日新月异&#xff0c;小程序逐渐成为广大企业和个人开发者的心仪之选。然而&#xff0c;伴随小程序的广泛应用&#xff0c;安全问题和用户信任显得尤为关键。为了确保小程序的信息传输安全&#xff0c;SSL证书成为了一项基础配置。那么…

Spring Cloud系列(二):Eureka Server应用

系列文章 Spring Cloud系列(一)&#xff1a;Spirng Cloud变化 Spring Cloud系列(二)&#xff1a;Eureka Server应用 目录 前言 注册中心对比 Nacos Zookeeper Consul 搭建服务 准备 搭建 搭建父模块 搭建Server模块 启动服务 测试 其他 前言 前面针对新版本的变化有了…

SD-WAN企业网络部署模式及适用企业类型

随着企业规模的扩张和数字化转型的推进&#xff0c;SD-WAN作为一种灵活、安全和高效的组网解决方案备受关注。那么&#xff0c;SD-WAN在企业网络部署中有哪些常见模式&#xff1f;又有哪些类型的企业最适合采用SD-WAN呢&#xff1f;让我们一起来了解一下&#xff01; 常见的SD-…

算法修炼之路之双指针含多道leetcode 经典题目

目录 前言 一&#xff1a;普通双指针 1.经典题目一 283移动0问题 分析 代码实现 2.经典题目二 1089复写0 分析 代码实现 二&#xff1a;解决成环类问题-快慢指针 经典例题一 202快乐数 分析 代码实现 三&#xff1a;左右相遇指针 经典例题一 11 盛最多水的容…

基于Whisper语音识别的实时视频字幕生成 (一): 流式显示视频帧和音频帧

Whishow Whistream&#xff08;微流&#xff09;是基于Whisper语音识别的的在线字幕生成工具&#xff0c;支持rtsp/rtmp/mp4等视频流在线语音识别 1. whishow介绍 whishow&#xff08;微秀&#xff09;是python实现的在线音视频流播放器&#xff0c;支持rtsp/rtmp/mp4等流式输…

说说TCP为什么需要三次握手和四次挥手?

文章目录 一、三次握手为什么不是两次握手? 二、四次挥手四次挥手原因 三、总结参考文献 一、三次握手 三次握手&#xff08;Three-way Handshake&#xff09;其实就是指建立一个TCP连接时&#xff0c;需要客户端和服务器总共发送3个包 主要作用就是为了确认双方的接收能力和…

毅速ESU丨增材制造有助于传统制造企业打造新增长极

在科技浪潮的推动下&#xff0c;传统制造企业正面临着前所未有的挑战与机遇。产品的复杂程度不断提升&#xff0c;个性化需求层出不穷&#xff0c;越来越短的生产周期&#xff0c;不断升级的品质要求等&#xff0c;传统的生产模式在应对这些变化并不容易。而增材制造&#xff0…

AI赋能校园管理,打造平安智慧校园解决方案

背景&#xff1a; 2020年教育部办公厅印发《教育系统安全专项整治三年行动实施方案》&#xff0c;文中要求&#xff0c;学校在所辖范围内组织开展安全专项整治三年行动&#xff0c;健全完善安全责任体系&#xff0c;建立风险管控和隐患治理的安全防控体系&#xff0c;开展消防等…

在线药房数据惨遭Ransomhub窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件119起&#xff0c;与上周相比勒索事件有所增长。 本周Blacksuit是影响最严重的勒索家族&#xff0c;Ransomhub和Blackbasta恶意家族紧随其后&#xff0c;从整体上看Lockbit3.0依旧是影响最严重的勒索家族&#xff0c;需要注意防范。…