Golang常见设计模式如何组合使用_多模式协作设计思路
技术百科
P粉602998670
发布时间:2026-01-25
浏览: 次 Factory 和 Strategy 应在需动态创建算法实例且算法内部依赖可替换行为时组合使用,如支付模块根据 payType 创建不同策略并注入签名器等依赖。
什么时候该把 Factory 和 Strategy 一起用
当你的业务逻辑需要根据运行时参数动态创建不同算法实现,且这些算法本身又需要封装可替换的行为时,Factory + Strategy 是最自然的组合。比如支付模块:用户选择微信支付或支付宝,系统要创建对应的支付策略实例,而每个策略内部还可能依赖不同的签名生成器、回调处理器。
-
Factory负责根据payType字符串返回具体的PaymentStrategy实现 -
Strategy接口定义Pay()和Refund(),但不关心如何序列化请求或验签 - 真正解耦的关键在于:Factory 创建 Strategy 时,可以传入其他依赖(比如一个
Signer),而这个Signer本身也可以是另一个 Strategy 或由 Builder 构建
type PaymentStrategy interface {
Pay(order *Order) error
}
type WechatPayStrategy struct {
signer Signer // 可替换的签名策略
}
func NewWechatPayStrategy(signer Signer) *WechatPayStrategy {
return &WechatPayStrategy{signer: signer}
}
func PaymentStrategyFactory(payType string) PaymentStrategy {
switch payType {
case "wechat":
return NewWechatPayStrategy(&WechatSHA256Signer{})
case "alipay":
return NewAlipayStrategy(&AlipayRSA2Signer{})

default:
panic("unknown pay type")
}
}
为什么 Observer 不该直接嵌套在 Singleton 里
常见错误是把事件监听器注册逻辑塞进单例的 GetInstance() 方法里,导致每次获取实例都重复绑定、内存泄漏、测试困难。Singleton 只应负责“唯一实例”的生命周期;Observer 关系必须可手动管理。
- 单例对象(如
Logger)暴露Subscribe()和Unsubscribe()方法,而不是自动绑定 - 订阅者应在初始化阶段显式调用,比如在
main()或模块init()中完成 - 若用
sync.Once初始化单例,切勿在其中调用任何可能阻塞或依赖外部状态的 Observer 注册逻辑
var loggerInstance *Logger
var loggerOnce sync.Once
func GetLogger() *Logger {
loggerOnce.Do(func() {
loggerInstance = &Logger{
subscribers: make(map[int]func(string)),
}
})
return loggerInstance
}
// 正确:由使用者决定何时订阅
func main() {
log := GetLogger()
log.Subscribe(1, func(msg string) { fmt.Println("[DEBUG]", msg) })
}
Decorator + Interface 嵌套容易踩的坑
Go 没有继承,靠接口组合实现 Decorator,但过度嵌套会导致方法爆炸和 nil panic。典型场景是给 HTTP handler 加日志、熔断、指标上报——每层 Decorator 都要完整实现 http.Handler 接口,哪怕只改一个方法。
- 避免让 Decorator 实现整个接口;优先用函数包装器(
func(http.Handler) http.Handler)而非结构体 - 如果必须用结构体 Decorator,确保其字段(如
next http.Handler)在构造时非 nil,并在ServeHTTP开头加if d.next == nil { panic(...) } - 多个 Decorator 组合时,顺序敏感:日志 Decorator 包裹熔断 Decorator,和反过来,行为完全不同
func WithMetrics(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 记录指标
next.ServeHTTP(w, r) // 注意:这里假设 next 不为 nil
})
}
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
// 正确顺序:指标 → 熔断 → 日志 → 原始 handler
http.ListenAndServe(":8080", WithMetrics(WithCircuitBreaker(WithLogging(h))))
}
Builder 在组合模式中不是万能的
Builder 适合构建复杂对象,但一旦和其他模式混用,容易变成“配置黑洞”。比如用 Builder 构建一个带多种 Strategy 的 Service 实例,最后却把所有策略类型、超时、重试次数全塞进 Builder 的链式调用里,导致可读性崩坏。
- Builder 应只负责“组装”,不负责“决策”;策略选择逻辑(如根据环境变量选重试策略)应放在 Builder 外部
- Builder 的
Build()方法不应做 I/O 或阻塞操作(比如读配置文件、连数据库),否则单元测试无法 mock - 如果 Builder 返回的对象本身还要被 Decorator 包裹,那 Builder 最好返回接口而非具体类型,否则 Decorator 得知道底层结构
复杂点往往不在模式本身,而在谁持有控制权——是 Builder 决定用哪个 Strategy,还是上层代码传进去。后者更可控,也更容易测试。
# ai
# 放在
# 多个
# 微信
# 链式
# 都要
# 绑定
# 而非
# 支付宝
# 什么时候
# 塞进
# 微信支付
# 配置文件
# http
# go
# golang
# 环境变量
# 对象
# if
# 字符串
# 接口
# nil
# 数据库
# 为什么
# 事件
# Interface
# 重试
# 封装
# 结构体
# 算法
# 继承
# switch
# 处理器
# 应在
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11如何设置环境变量 Win11添加和修改系
- c++ std::atomic如何保证原子性 c+
- 如何使用Golang实现容器自动化运维_Golan
- PowerShell怎么创建复杂的XML结构
- c++如何使用std::bitset进行位图算法_
- Win11怎么关闭系统推荐内容_Windows11
- Win10系统怎么查看端口状态_Windows10
- MySQL 中使用 IF 和 CASE 实现查询字
- 如何在Golang中验证模块完整性_Golangg
- 如何更改Windows资源管理器的默认启动位置?(
- php文件怎么变mp4保存_php输出视频流保存为
- Win11怎么卸载Photos应用_Win11卸载
- Win11如何设置计划任务 Win11定时执行程序
- php嵌入式多设备通信怎么实现_php同时管理多个
- 如何使用Golang实现RPC序列化与反序列化_G
- c++如何获取map中所有的键_C++遍历键值对提
- Win11怎么退出微软账户_切换Win11为本地账
- MAC怎么解压RAR格式文件_MAC第三方解压工具
- c++如何使用std::bind绑定函数参数_c+
- Windows 10怎么把任务栏放在屏幕上方_Wi
- Win11输入法切换快捷键怎么改_Windows
- Win10怎样卸载自带Edge_Win10卸载Ed
- Windows10系统怎么查看防火墙状态_Win1
- C#如何在一个XML文件中查找并替换文本内容
- Win11怎么开启游戏工具栏_Windows11
- Win11怎么关闭自动调节亮度_Windows11
- Windows10电脑怎么连接蓝牙设备_Win10
- Win11怎么开启剪贴板历史记录_Windows1
- VSC怎么创建PHP项目_从零开始搭建项目的步骤【
- 如何使用 Python 合并文件夹内多个 Exce
- C++如何将C风格字符串(char*)转换为std
- 如何在Golang中解压文件_Golang com
- php怎么连接数据库_MySQL数据库连接的基础代
- c++ nullptr与NULL区别_c++11空
- Windows如何拦截2345弹窗广告_Windo
- 如何在 Go 后端安全获取并验证前端存储的 JWT
- windows 10专注助手怎么关闭_window
- Python与OpenAI接口集成实战_生成式AI
- 如何使用Golang构建简易投票统计功能_Gola
- c++中如何使用std::variant_c++1
- PHP主流架构怎么部署到Docker_容器化流程【
- Windows11怎样开启游戏模式_Windows
- Win10如何更改网络连接_Windows10以太
- Python网页解析流程_html结构说明【指导】
- c++中如何计算坐标系中两点间距离_c++勾股定理
- Python如何创建带属性的XML节点
- Win11如何设置系统声音_Win11系统声音调整
- 如何在Golang中实现微服务服务拆分_Golan
- c++中explicit(bool)的用法 c++
- Win10如何更改用户账户控制_Windows10


QQ客服