デザインパターンは「よくある設計の問題と解決策」を名前付きで整理したものです。JavaScriptで頻出する10のパターンを実装例つきで紹介します。本記事を読めば、コードの「読みやすさ」「拡張のしやすさ」を一段上げられます。
1. Singleton(シングルトン)
const config = {
load() { /* ... */ }
};
// ESMはモジュール自体がSingletonとして機能
2. Factory(ファクトリ)
function createUser(type) {
if (type === 'admin') return { role: 'admin', perms: ['*'] };
return { role: 'user', perms: ['read'] };
}
3. Observer(オブザーバ)
class EventBus {
constructor() { this.subs = {}; }
on(ev, fn) { (this.subs[ev] ??= []).push(fn); }
emit(ev, data) { (this.subs[ev] ?? []).forEach(fn => fn(data)); }
}
4. Strategy(ストラテジー)
const strategies = {
jp: price => price * 1.1,
us: price => price * 1.08,
};
function calc(price, country) {
return strategies[country](price);
}
5. Adapter(アダプタ)
class OldLogger { writeLog(msg) {} }
class LoggerAdapter {
constructor(old) { this.old = old; }
log(msg) { this.old.writeLog(msg); }
}
6. Decorator(デコレータ)
function withLogging(fn) {
return (...args) => {
console.log('call', fn.name, args);
const r = fn(...args);
console.log('=>', r);
return r;
};
}
const log = withLogging(Math.sqrt);
7. Module(モジュール)
ESModulesでファイルごとに公開APIを絞ることがそのままModuleパターンになります。exportするものだけが外部から見える、という基本原則です。
8. Builder(ビルダー)
class QueryBuilder {
constructor() { this.q = {}; }
where(k, v) { this.q.where = { ...this.q.where, [k]: v }; return this; }
limit(n) { this.q.limit = n; return this; }
build() { return this.q; }
}
new QueryBuilder().where('active', true).limit(10).build();
9. Proxy(プロキシ)
const logged = new Proxy(target, {
get(obj, prop) {
console.log('read', prop);
return obj[prop];
}
});
10. State(ステート)
const states = {
idle: { start: 'running' },
running: { pause: 'paused', stop: 'idle' },
paused: { resume: 'running', stop: 'idle' },
};
function transition(state, event) {
return states[state][event] ?? state;
}
注意点
- パターン適用ありき設計はオーバーエンジニアリングを生みやすい
- 「名前を知っている」だけでもチーム内コミュニケーションが滑らかに
- JavaScript独自の関数・クロージャ・PrototypeでGoF風と異なる実装になることがある
まとめ
デザインパターンは「設計の語彙」を増やしてくれます。必要な時に必要なものを採用が原則で、無理に当てはめる必要はありません。Observer・Strategy・Factoryあたりは特に頻出なので、まずこの3つだけでも自分のものにすると効果絶大です。