HelloWorld 设计模式实战教程

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

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 固定流程,变动步骤 算法骨架化

实战技巧:如何把模式落地到真实项目

  • 从需求出发:不要为模式而模式。先有痛点,再看哪个模式合适。
  • 小步重构:先可运行的实现,然后提取接口/抽象,逐步引入模式。
  • 写测试:模式重构后,单元测试能快速校验行为不变。
  • 保持简单:万一一个模式让代码复杂度大增,那就撤回去。
  • 文档化意图:在代码注释或设计文档写清为什么用这个模式,方便以后维护。

常见误区与反模式

  • 过早抽象:过度设计会浪费时间且增加认知负担。
  • 滥用单例:把所有工具都写成单例,会导致模块耦合和测试困难。
  • 把模式当框架:模式是工具,不是替代良好架构的万能药。

如何练习(步骤与练习题)

  1. 写一个最简单的 HelloWorld 输出。
  2. 需求变化:需要多渠道输出,先用 if/else 实现,然后用工厂重构。
  3. 再需求变化:要在输出前后加处理(如时间戳、安全检查),试试装饰者。
  4. 增加实时订阅需求:用观察者把发布/订阅加上。
  5. 把代码翻译到另一种语言(如 Java → JavaScript),体会模式语言无关性。

学习资源(可选读物)

  • 《设计模式:可复用面向对象软件的基础》(GoF)
  • 《Head First Design Patterns》
  • 《重构:改善既有代码的设计》

这样一路实践下来,你会发现模式不是死记硬背的名单,而是「解决问题的语言」。每次你在代码里看到重复的变化点,就能问自己:哪个模式能把这部分抽离、让未来变更更可控?然后一步步去改造、跑测试、观察副作用。确实有时候会折腾几次才对,但这是正常的学习曲线——写着写着就懂了,就像刚开始把 HelloWorld 从终端搬到网页上那会儿,慢慢就能把复杂系统拆成一组可理解的小片段。就先写到这儿,回头我还想把一些具体的重构提交记录贴出来参考。