设计模式核心:从创建型到行为型

举报
yd_236430038 发表于 2026/11/05 20:06:12 2026/11/05
【摘要】 设计模式核心:从创建型到行为型本文不罗列 23 个模式的八股定义,而是讲清它们各自解决什么问题、什么时候不该用,以及在现代语言里哪些模式已经被语法本身取代。 先分清:模式解决的是"变化"设计模式的本质是把"会变化的部分"和"稳定的部分"分开。判断一个模式该不该用,只看一件事:这里有没有需要独立变化、且未来可能扩展的维度。没有变化点硬套模式,只会增加一层无谓的间接。SOLID 是这套思想的原...

设计模式核心:从创建型到行为型

本文不罗列 23 个模式的八股定义,而是讲清它们各自解决什么问题、什么时候不该用,以及在现代语言里哪些模式已经被语法本身取代。

先分清:模式解决的是"变化"

设计模式的本质是把"会变化的部分"和"稳定的部分"分开。判断一个模式该不该用,只看一件事:这里有没有需要独立变化、且未来可能扩展的维度。没有变化点硬套模式,只会增加一层无谓的间接。

SOLID 是这套思想的原则版:

  • 单一职责:一个类只有一个变化原因。
  • 开闭:对扩展开放,对修改关闭。
  • 里氏替换:子类能替换父类而不破坏行为。
  • 接口隔离:接口要小而专。
  • 依赖倒置:依赖抽象,不依赖实现。

创建型:怎么把对象造出来

工厂方法

当创建逻辑复杂、或需要按类型返回不同实现时,把 new 收进工厂。

type Notifier interface{ Send(msg string) error }

func NewNotifier(kind string) Notifier {
    switch kind {
    case "sms":
        return &SMSNotifier{}
    case "email":
        return &EmailNotifier{}
    default:
        return &NoopNotifier{}
    }
}

适用:调用方只关心接口,不关心具体类型。过度使用会让简单 new 变成一堆工厂类。

建造者

对象参数多、可选参数多、构造步骤有顺序时,用链式建造者避免"构造函数参数爆炸"。

Order order = Order.builder()
    .userId("u1")
    .items(items)
    .coupon("C10")
    .build();

Go 里常用函数式选项(functional options)达到同样效果。

单例

全局只允许一个实例。问题在于它引入了全局状态和隐式依赖,测试困难、并发初始化要加锁。现代做法通常是依赖注入容器管理生命周期,而不是到处 getInstance()。

结构型:怎么把对象组合起来

适配器

把不兼容的接口转成目标接口。典型场景是接入第三方 SDK、兼容旧接口。

type LegacyLogger struct{}
func (l *LegacyLogger) WriteLog(s string) { /* ... */ }

type Logger interface{ Info(msg string) }

type LoggerAdapter struct{ legacy *LegacyLogger }
func (a *LoggerAdapter) Info(msg string) { a.legacy.WriteLog("[INFO] " + msg) }

装饰器

在不修改原对象的前提下动态叠加能力,比如给 HTTP handler 加日志、鉴权、限流。Go 的中间件、Python 的 @decorator 都是这个思路。

func WithLogging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

代理

在真实对象前加一层,控制访问:延迟加载、权限校验、远程调用、缓存。和装饰器的区别是目的不同——装饰器加功能,代理控访问。

组合

把树形结构统一对待,叶子节点和容器节点实现同一接口。文件系统、组织架构、UI 组件树都是典型。

行为型:怎么分配职责与通信

策略

把一组可互换的算法封装起来,运行时选择。替代冗长的 if/else 或 switch。

type Pricing interface{ Calc(amount float64) float64 }

type NormalPricing struct{}
func (NormalPricing) Calc(a float64) float64 { return a }

type VIPPricing struct{}
func (VIPPricing) Calc(a float64) float64 { return a * 0.8 }

func Price(p Pricing, amount float64) float64 { return p.Calc(amount) }

观察者

一对多依赖,状态变化时通知所有订阅者。事件总线、消息推送、前端响应式系统都是它。注意控制通知顺序和异常隔离,否则一个订阅者报错会拖垮整条链路。

责任链

请求沿链传递,直到被处理。中间件链、审批流、日志分级处理都适用。要防止链过长导致排查困难,并明确"无人处理"时的兜底。

模板方法

父类定义流程骨架,子类实现可变步骤。适合流程固定、细节可变的场景,比如数据导入导出、审批流程。缺点是继承耦合强,现代更倾向用组合 + 策略替代。

哪些模式正在被语言特性取代

传统模式 现代替代
策略 / 命令 一等函数、闭包、lambda
工厂 依赖注入容器、泛型
单例 容器管理的单例作用域
迭代器 语言内置 for-range / iterator
观察者 响应式库、事件总线、Channel

这不是说模式没用了,而是不要为了"用模式"而写模式。语言已经表达清楚的地方,再加一层抽象是负担。

反模式:比不用模式更糟

  • 过度抽象:为不存在的扩展点设计接口,读代码要跳五层。
  • 万能基类:所有类继承一个巨型 Base,改一处影响全站。
  • 隐式全局状态:单例满天飞,测试互相污染。
  • 模式堆砌:一个类同时是工厂、策略、观察者,谁也看不懂。
  • 为了设计模式而设计模式:面试背得熟,线上没人维护。

实战:怎么判断该不该上模式

  1. 先写最直白的实现,能跑通再谈抽象。
  2. 出现第二处相似变化时,再抽接口(三次法则)。
  3. 抽象后如果调用方更难看懂,说明抽象错了。
  4. 优先组合而非继承,优先函数而非类。
  5. 模式名只用于沟通,不写进类名(XxxFactory 泛滥是坏味道)。

总结

类别 代表模式 解决的问题
创建型 工厂、建造者、单例 对象创建复杂或需受控
结构型 适配器、装饰器、代理、组合 对象组合与接口适配
行为型 策略、观察者、责任链、模板方法 职责分配与通信

设计模式是"变化点的工具箱",不是代码模板。真正重要的是识别哪里会变、把变化隔离开,至于是不是叫某个模式名,并不重要。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。