javaScript設計模式 : GoF 設計模式實作整理

 


談 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設計模式 : Facade(外觀模式)

javaScript設計模式 : Flyweight(享元模式)

javaScript設計模式 : Proxy(代理模式)

javaScript設計模式 : Chain of Responsibility(責任鏈)

javaScript設計模式 : Command Pattern(命令模式)

javaScript設計模式 : Interpreter(直譯器)

javaScript設計模式 : Iterator(迭代器)

javaScript設計模式 : Mediator(仲裁者)

javaScript設計模式 : Memento(備忘錄)

javaScript設計模式 : Observer( 觀察者 )

javaScript設計模式 : State(狀態模式)

javaScript設計模式 : Strategy(策略模式)

javaScript設計模式 : Template Method (模板方法)

javaScript設計模式 : Visitor(訪問者模式)


張貼留言 (0)
較新的 較舊