设计模式不是魔法,它是把重复出现的设计问题抽象成清晰的解决套路。用“HelloWorld”做起点,把每个模式先用最简单的输出实现(先能跑、再重构),通过小而明确的示例理解意图、参与者和适用场景,再逐步扩展到真实需求,这样学会的东西既能记住也能用上,并在实践中成长吧

先说为什么:设计模式到底帮你解决啥
简单来说,设计模式是前人总结出的“成熟做法”。不是教你写复杂语法,而是教你在面对类似问题时怎么组织代码符号化地思考。想象你要把“HelloWorld”输出到不同渠道——终端、网页、日志文件、远程服务——模式帮你把变与不变分离,让新增渠道不毁掉已有代码。
用 HelloWorld 学习设计模式的思路(费曼法则式)
- 先最简单能运行:写一个直接输出“HelloWorld”的函数或类,确认需求。
- 提问并解释:为什么要改变?扩展点在哪里?谁负责变化?把答案说给自己听。
- 逐步引入模式:引入一个模式,改造代码,跑一遍,观察变化,写小测试。
- 复述与类比:把模式用一句话讲给同事或纸上写下,就像给初学者解释一样。
- 反复练习:把同样的模式在不同语言或不同场景复现,看看差异。
实战模式示例(每个都用 HelloWorld 场景)
1. 单例(Singleton)
意图:保证一个类只有一个实例,并提供全局访问点。HelloWorld 场景:日志管理器、配置管理器。
// JavaScript 简单版
class Logger {
constructor(){ if (Logger.instance) return Logger.instance; Logger.instance=this; }
log(msg){ console.log(msg); }
}
const a = new Logger(); const b = new Logger(); a.log("HelloWorld");
要点:避免滥用,全局状态会导致测试困难。懒汉式、饿汉式、线程安全都是变种。
2. 工厂方法 / 简单工厂(Factory)
意图:将对象创建封装,客户端不直接 new,而是通过工厂获取实例。HelloWorld 场景:选择输出目标(console、DOM、file)。
// 简单工厂
function createPrinter(type){
if(type === 'console') return msg => console.log(msg);
if(type === 'alert') return msg => alert(msg);
}
const p = createPrinter('console'); p('HelloWorld');
好处:添加新类型只改工厂或注册表,避免散落的 if/else。
3. 策略模式(Strategy)
意图:定义一系列算法并让它们可互换。HelloWorld 场景:不同格式化策略(原文、加前缀、国际化)。
// 策略模式
const strategies = {
plain: s => s,
prefix: s => '[INFO] ' + s,
i18n: s => ({en:'HelloWorld', zh:'你好世界'})['zh']
};
function print(text, strategy) { console.log(strategies[strategy](text)); }
print('HelloWorld','prefix');
使用场景:当行为会变化且需要在运行时替换时。
4. 观察者(Observer / Pub-Sub)
意图:定义对象间一对多依赖,状态改变通知所有依赖者。HelloWorld 场景:多处订阅“新消息”事件来显示 HelloWorld。
// 简单发布订阅
class PubSub{
constructor(){ this.list={}; }
subscribe(evt,fn){ (this.list[evt]||(this.list[evt]=[])).push(fn); }
publish(evt,data){ (this.list[evt]||[]).forEach(fn=>fn(data)); }
}
const bus=new PubSub();
bus.subscribe('msg', m=>console.log('UI:',m));
bus.subscribe('msg', m=>console.log('Log:',m));
bus.publish('msg','HelloWorld');
注意内存泄漏:订阅后忘记取消是常见陷阱。
5. 装饰者(Decorator)
意图:动态地给对象添加职责,而不改变原对象。HelloWorld 场景:给打印行为附加时间戳、颜色或写入文件。
// 装饰者链
const base = msg => console.log(msg);
const timeDecor = fn => msg => fn(new Date().toISOString() + ' ' + msg);
const colorDecor = fn => msg => fn('%c' + msg, 'color:green');
const decorated = timeDecor(colorDecor(base));
decorated('HelloWorld');
比继承灵活,能在运行时组合功能。
6. 适配器(Adapter)
意图:把一个接口转换成客户端期望的另一个接口。HelloWorld 场景:将第三方库的输出 API 转成你统一的 print(apiMsg)。
// 适配器示意
const thirdLib = { send: txt => {/*...*/} };
function adapter(text){ thirdLib.send(text); }
adapter('HelloWorld');
适配器不会修改被适配对象,常用于兼容遗留代码。
7. 模板方法(Template Method)
意图:定义算法骨架,把可变步骤留给子类实现。HelloWorld 场景:定义打印流程(获取内容、格式化、输出),把格式化留给子类。
// 伪代码
class Printer {
print(){ const raw=this.fetch(); const formatted=this.format(raw); this.output(formatted); }
fetch(){ return 'HelloWorld'; }
format(raw){ return raw; } // 子类覆盖
}
当有固定流程但步骤可变时好用。
一张表快速回顾
| 模式 | 意图 | 适用场景 |
| Singleton | 唯一实例,全局访问 | 日志、配置管理 |
| Factory | 封装创建 | 多种产品的选择 |
| Strategy | 算法可替换 | 行为可切换 |
| Observer | 一对多通知 | 事件驱动、UI 更新 |
| Decorator | 动态添加职责 | 功能组合、AOP 风格 |
| Adapter | 接口转换 | 第三方集成、遗留系统 |
| Template | 固定流程,变动步骤 | 算法骨架化 |
实战技巧:如何把模式落地到真实项目
- 从需求出发:不要为模式而模式。先有痛点,再看哪个模式合适。
- 小步重构:先可运行的实现,然后提取接口/抽象,逐步引入模式。
- 写测试:模式重构后,单元测试能快速校验行为不变。
- 保持简单:万一一个模式让代码复杂度大增,那就撤回去。
- 文档化意图:在代码注释或设计文档写清为什么用这个模式,方便以后维护。
常见误区与反模式
- 过早抽象:过度设计会浪费时间且增加认知负担。
- 滥用单例:把所有工具都写成单例,会导致模块耦合和测试困难。
- 把模式当框架:模式是工具,不是替代良好架构的万能药。
如何练习(步骤与练习题)
- 写一个最简单的 HelloWorld 输出。
- 需求变化:需要多渠道输出,先用 if/else 实现,然后用工厂重构。
- 再需求变化:要在输出前后加处理(如时间戳、安全检查),试试装饰者。
- 增加实时订阅需求:用观察者把发布/订阅加上。
- 把代码翻译到另一种语言(如 Java → JavaScript),体会模式语言无关性。
学习资源(可选读物)
- 《设计模式:可复用面向对象软件的基础》(GoF)
- 《Head First Design Patterns》
- 《重构:改善既有代码的设计》
这样一路实践下来,你会发现模式不是死记硬背的名单,而是「解决问题的语言」。每次你在代码里看到重复的变化点,就能问自己:哪个模式能把这部分抽离、让未来变更更可控?然后一步步去改造、跑测试、观察副作用。确实有时候会折腾几次才对,但这是正常的学习曲线——写着写着就懂了,就像刚开始把 HelloWorld 从终端搬到网页上那会儿,慢慢就能把复杂系统拆成一组可理解的小片段。就先写到这儿,回头我还想把一些具体的重构提交记录贴出来参考。