談 GoF 設計模式,很多人第一反應是「那不是 Java、C# 的東西嗎?」其實 JavaScript 全做得到,只是做法更貼近它自己的語感:原型、模組單例、高階函式、Proxy、Generator… 都能把老派模式轉成好維護的寫法。
這篇會把 23 個經典模式一次跑過,重點不在背定義,而是看「什麼場景該用」「在 JS 該怎麼寫」「什麼時候乾脆不用」。每個模式都會有最小可運作範例(能直接貼進專案測),附上常見踩雷與替代解法。
你會看到 Decorator 用函式就能優雅收尾、Singleton 交給 ES 模組最自然、Iterator 用 generator 最乾淨。看完不只補齊觀念,更拿得到能上線的程式片段。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
GoF 設計模式的類型
建立型(Creational)
1. Factory Method:用 工廠函式 或 static create()。
2. Abstract Factory:回傳一組相容物件的工廠集合(函式組)。
3. Builder:鏈式 API 或不可變物件的 step-by-step 組裝。
4. Prototype:Object.create(proto)、自訂 clone()、或 structuredClone。
5. Singleton:ES 模組天生單例(匯入同一個實例)。
結構型(Structural)
6. Adapter:包一層把 A 介面轉成 B 介面(函式或 class 包裝)。
7. Bridge:抽離抽象與實作(策略注入)。
8. Composite:樹狀結構,節點/葉子共享介面(DOM 就是範例)。
9. Decorator:高階函式或物件組合,比傳統繼承更自然。
10. Facade:對外提供簡化 API 的門面模組。
11. Flyweight:用 Map 快取共享內部狀態,避免重複建立。
12. Proxy:ES Proxy 內建,攔截存取、做延遲載入/驗證/快取。
行為型(Behavioral)
13. Chain of Responsibility:陣列 handlers 依序嘗試處理。
14. Command:把操作封裝成物件/函式,可做佇列/復原。
15. Interpreter:小語法解析器(組合子/正則/AST)。
16. Iterator:內建迭代協定([Symbol.iterator])與 generator。
17. Mediator:集中協調者(事件匯流排 / 中樞物件)。
18. Memento:快照(JSON/深拷貝)+還原點。
19. Observer:DOM 事件、Node EventEmitter、RxJS 都是觀察者。
20. State:以物件/表格化轉移管理狀態。
21. Strategy:把可替換算法當參數注入。
22. Template Method:父類骨架 + 可覆寫步驟;或以「流程函式 + hooks」實作。
23. Visitor:對樹狀/異質結構分派執行;JS 常用「模式比對 + 函式表」。
建立型(Creational)
1. Factory Method(工廠方法)
// 產品
class Button { click() { return 'clicked'; } }
class Link { click() { return 'navigated'; } }
// Creator:定義工廠方法
class WidgetFactory {
create(type) {
if (type === 'button') return new Button();
if (type === 'link') return new Link();
throw new Error('unknown type');
}
}
// 用法
const f = new WidgetFactory();
console.log(f.create('button').click()); // "clicked"
2. Abstract Factory(抽象工廠)
// 一組相容的產品族
const lightThemeFactory = () => ({
button: () => ({ render: () => 'btn(light)' }),
input: () => ({ render: () => 'input(light)' })
});
const darkThemeFactory = () => ({
button: () => ({ render: () => 'btn(dark)' }),
input: () => ({ render: () => 'input(dark)' })
});
// 用法
function renderUI(factory) {
const b = factory.button(), i = factory.input();
return [b.render(), i.render()].join(' | ');
}
console.log(renderUI(lightThemeFactory())); // btn(light) | input(light)
3. Builder(產生器)
class Query {
constructor({ table, fields='*', where='' }) {
this.table = table; this.fields = fields; this.where = where;
}
toString() { return `SELECT ${this.fields} FROM ${this.table}${this.where?` WHERE ${this.where}`:''}`; }
}
class QueryBuilder {
reset(){ this._table=''; this._fields='*'; this._where=''; return this; }
table(name){ this._table=name; return this; }
fields(...fs){ this._fields=fs.join(','); return this; }
where(expr){ this._where=expr; return this; }
build(){ return new Query({ table:this._table, fields:this._fields, where:this._where }); }
}
const sql = new QueryBuilder().reset().table('users').fields('id','name').where('id>10').build();
console.log(sql.toString()); // SELECT id,name FROM users WHERE id>10
4. Prototype(原型)
const proto = {
greet(){ return `hi, ${this.name}`; }
};
const user1 = Object.create(proto, { name:{ value:'Ada', writable:true, enumerable:true } });
const user2 = Object.assign(Object.create(Object.getPrototypeOf(user1)), JSON.parse(JSON.stringify(user1)));
user2.name = 'Grace';
console.log(user1.greet(), '|', user2.greet()); // hi, Ada | hi, Grace
5. Singleton(單例)
在 ES 模組中,從同一檔案 import 取得天然單例。這裡示範閉包單例:
const AppConfig = (() => {
let instance;
return {
get() {
if (!instance) instance = { env: 'prod', version: '1.0.0' };
return instance;
}
};
})();
console.log(AppConfig.get() === AppConfig.get()); // true
結構型(Structural)
6. Adapter(轉接器)
// 舊介面
class LegacyPay { send(amount){ return `legacy:${amount}`; } }
// 目標介面:pay(amount)
const adapter = legacy => ({ pay:(amount)=>legacy.send(amount) });
// 用法
const pay = adapter(new LegacyPay());
console.log(pay.pay(100)); // legacy:100
7. Bridge(橋接)
// 實作端
class EmailSender { send(msg){ return `email:${msg}`; } }
class SmsSender { send(msg){ return `sms:${msg}`; } }
// 抽象端
class Notifier {
constructor(sender){ this.sender = sender; }
notify(msg){ return this.sender.send(msg); }
}
console.log(new Notifier(new EmailSender()).notify('hi')); // email:hi
8. Composite(合成)
class Node {
constructor(name){ this.name=name; this.children=[]; }
add(child){ this.children.push(child); return this; }
render(depth=0){
const me = ' '.repeat(depth)+this.name;
return [me, ...this.children.flatMap(c=>c.render(depth+1))];
}
}
// 用法
const root = new Node('root').add(new Node('a')).add(new Node('b').add(new Node('b1')));
console.log(root.render().join('\n'));
9. Decorator(裝飾)
// 函式裝飾器:加計時
const withTiming = fn => (...args) => {
const t0 = performance.now(); const out = fn(...args);
console.log('took', performance.now()-t0, 'ms'); return out;
};
const heavy = n => Array.from({length:n}, (_,i)=>i).reduce((a,b)=>a+b,0);
withTiming(heavy)(1e5);
10. Facade(外觀)
// 子系統
const db = { save: x => `saved:${x}` };
const mail = { send: to => `mail->${to}` };
// 外觀
const service = {
signup(userEmail){
const r1 = db.save(userEmail);
const r2 = mail.send(userEmail);
return [r1, r2].join(' | ');
}
};
console.log(service.signup('a@b.com')); // saved:a@b.com | mail->a@b.com
11. Flyweight(享元)
class Icon {
constructor(type){ this.type=type; } // 內部狀態(共享)
draw(x,y){ return `${this.type}@${x},${y}`; } // 外部狀態(座標)由呼叫方提供
}
class IconFactory {
cache = new Map();
get(type){ if(!this.cache.has(type)) this.cache.set(type,new Icon(type)); return this.cache.get(type); }
}
const f = new IconFactory();
const a = f.get('file'), b = f.get('file');
console.log(a===b, a.draw(1,2)); // true "file@1,2"
12. Proxy(代理)
const user = { name:'Ada', age:30 };
const safe = new Proxy(user, {
set(t,k,v){ if(k==='age' && v<0) throw new Error('age?'); t[k]=v; return true; },
get(t,k){ console.log('get',k); return t[k]; }
});
safe.age = 31; // OK
console.log(safe.name); // 觸發 get
行為型(Behavioral)
13. Chain of Responsibility(職責鏈)
const h1 = (req,next)=> req.type==='a' ? 'A-done' : next();
const h2 = (req,next)=> req.type==='b' ? 'B-done' : next();
const h3 = (req,next)=> 'fallback';
const chain = (...handlers) => req => {
let i=0; const next = () => handlers[i++]?.(req,next);
return next();
};
console.log(chain(h1,h2,h3)({type:'b'})); // B-done
14. Command(命令)
class Counter { constructor(){ this.v=0; } }
const Inc = c => ({ execute:()=>c.v++, undo:()=>c.v-- });
const Add = (c,n) => ({ execute:()=>c.v+=n, undo:()=>c.v-=n });
class Invoker {
stack=[]; run(cmd){ cmd.execute(); this.stack.push(cmd); }
undo(){ this.stack.pop()?.undo(); }
}
const c = new Counter(), i = new Invoker();
i.run(Inc(c)); i.run(Add(c,5));
console.log(c.v); // 6
i.undo(); console.log(c.v); // 1
15. Interpreter(直譯器)
迷你算式:只支援加法與整數("3+5+2")。
class Context { constructor(text){ this.tokens=text.split('+').map(Number); } }
class Expression { interpret(ctx){ return ctx.tokens.reduce((a,b)=>a+b,0); } }
console.log(new Expression().interpret(new Context('3+5+2'))); // 10
16. Iterator(迭代器)
// 自訂可迭代物件
const range = (a,b) => ({
[Symbol.iterator]: function* () { for(let i=a;i<=b;i++) yield i; }
});
for (const n of range(3,5)) console.log(n); // 3 4 5
17. Mediator(仲介者)
class Chat {
constructor(){ this.clients=new Set(); }
join(c){ this.clients.add(c); c.chat=this; }
send(from,msg){ for(const c of this.clients) if(c!==from) c.receive(from.name, msg); }
}
class Client {
constructor(name){ this.name=name; }
say(msg){ this.chat.send(this,msg); }
receive(from,msg){ console.log(`[${from}] ${msg}`); }
}
const chat = new Chat(), a=new Client('A'), b=new Client('B');
chat.join(a); chat.join(b);
a.say('hi'); // [B] hi
18. Memento(備忘錄)
class Editor {
constructor(){ this.text=''; }
type(t){ this.text+=t; }
save(){ return { text:this.text }; } // Memento
restore(m){ this.text=m.text; }
}
const e = new Editor();
e.type('hello'); const m = e.save();
e.type(' world'); e.restore(m);
console.log(e.text); // hello
19. Observer(觀察者)
class EventBus {
subs = new Map();
on(evt, fn){ (this.subs.get(evt)??this.subs.set(evt,[]).get(evt)).push(fn); }
emit(evt, data){ (this.subs.get(evt)||[]).forEach(fn=>fn(data)); }
}
const bus = new EventBus();
bus.on('pay', x => console.log('paid', x));
bus.emit('pay', { id:1, amount:99 });
20. State(狀態)
class Draft { publish(ctx){ ctx.setState(new Published()); return '->published'; } }
class Published { archive(ctx){ ctx.setState(new Archived()); return '->archived'; } }
class Archived {}
class Post {
constructor(){ this.setState(new Draft()); }
setState(s){ this.state=s; }
publish(){ return this.state.publish?.(this) ?? 'invalid'; }
archive(){ return this.state.archive?.(this) ?? 'invalid'; }
}
const p = new Post();
console.log(p.publish()); // ->published
console.log(p.archive()); // ->archived
21. Strategy(策略)
const full = a => a; const tenOff = a => a*0.9; const checkout = (amount, strategy=full) => strategy(amount); console.log(checkout(100, tenOff)); // 90
22. Template Method(範本方法)
class Report {
generate() { return [this.header(), this.body(), this.footer()].join('\n'); }
header(){ return '=== Report ==='; }
// 子類覆寫
body(){ throw new Error('body not implemented'); }
footer(){ return '=== End ==='; }
}
class SalesReport extends Report {
body(){ return 'sales: 100'; }
}
console.log(new SalesReport().generate());
23. Visitor(訪問者)
// AST 節點
class Num { constructor(n){ this.n=n; } accept(v){ return v.visitNum(this); } }
class Add { constructor(l,r){ this.l=l; this.r=r; } accept(v){ return v.visitAdd(this); } }
// Visitor:計算值
class EvalVisitor {
visitNum(n){ return n.n; }
visitAdd(a){ return a.l.accept(this) + a.r.accept(this); }
}
// Visitor:列印
class PrintVisitor {
visitNum(n){ return String(n.n); }
visitAdd(a){ return `(${a.l.accept(this)}+${a.r.accept(this)})`; }
}
// 用法: (1 + (2 + 3))
const expr = new Add(new Num(1), new Add(new Num(2), new Num(3)));
console.log(new EvalVisitor().visitAdd(expr)); // 6
console.log(new PrintVisitor().visitAdd(expr)); // (1+(2+3))
小結與使用建議
都能用 JS 實作,但別硬抄教科書;像 Decorator、Strategy、Chain 這些在 JS 會更自然用「高階函式+組合」處理。
單例優先用 ES 模組、裝飾優先用函式、迭代器優先用 generator、代理用 Proxy。
延伸閱讀推薦:
javaScript設計模式 : Factory Method (工廠方法)
javaScript設計模式 : Abstract Factory(抽象工廠)
javaScript設計模式 : Builder(建造者模式)
javaScript設計模式 : Prototype(原型)
javaScript設計模式 : Singleton (單例模式)
javaScript設計模式 : Adapter(轉接器模式)
javaScript設計模式 : Bridge( 橋接模式 )
javaScript設計模式 : Composite(組合模式)
javaScript設計模式 : Decorator(裝飾者)
javaScript設計模式 : Flyweight(享元模式)
javaScript設計模式 : Chain of Responsibility(責任鏈)
javaScript設計模式 : Command Pattern(命令模式)
javaScript設計模式 : Interpreter(直譯器)
javaScript設計模式 : Iterator(迭代器)
javaScript設計模式 : Mediator(仲裁者)
javaScript設計模式 : Observer( 觀察者 )
javaScript設計模式 : Strategy(策略模式)
