JavaScript で実現する主要なデザインパターンの実装ガイド

1. ファクトリパターン

ファクトリパターンは、複雑なオブジェクト生成処理を単一の関数に集約する設計手法です。複数の類似したオブジェクトを作成する際、クラス名の直接指定を防ぎ、システムのカプセル化を促進します。

以下は、基本となる簡易ファクトリの例です。ここではユーザー情報を生成するプロセスを例示します。


// 単純な製造メソッド
function createUser(type, data) {
    let user = {};
    
    // タイプに応じて初期値を定義
    if (type === 'admin') {
        user.role = 'admin';
    } else {
        user.role = 'user';
    }
    
    return Object.assign(user, data);
}

const userA = createUser('admin', { id: 1, name: 'Manager' });
const userB = createUser('user', { id: 2, name: 'Guest' });

console.log(userA.role); // admin
console.log(userB.role); // user

この方式では、すべてのオブジェクトが同じ構造体から派生しますが、内部の具体的なクラスやインスタンスの種類が呼び出し元から隠蔽されます。ただし、タイプによって振る舞いが異なる場合や、拡張性を高める必要がある場合は、抽象ファクトリを用いるアプローチが推奨されます。

2. シングルトンパターン

シングルトンパターンは、アプリケーション全体で特定のクラスのインスタンスをただ一つしか持たないように保証するパターンです。設定マネージャーやデータベース接続など、グローバルな状態を保持する際に有効です。

closures を利用して、一度生成されたインスタンスを保持し、次回以降は返す方式が一般的です。


// 汎用的なシングルトン取得ユーティリティ
const getInstanceFactory = (function() {
    let instance = null;
    
    return function(Constructor, ...args) {
        if (!instance) {
            instance = new Constructor(...args);
        }
        return instance;
    };
})();

// 使用例:ログ機能を持つクラス
class LoggerService {
    constructor(prefix) {
        this.prefix = prefix;
    }
    log(message) {
        console.log(`[${this.prefix}] ${message}`);
    }
}

// インスタンス化されるのは一度きり
const logger1 = getInstanceFactory(LoggerService, 'System');
const logger2 = getInstanceFactory(LoggerService, 'Auth');

console.log(logger1 === logger2); // true
logger1.log("Hello World");      // [System] Hello World

また、DOM 操作に関しても、要素を再利用することでパフォーマンス向上を図ることができます。例えば、モーダルウィンドウをページ上で重複して作成せず、一度作成して表示・非表示のみを制御する実装が可能です。


const createModal = (function() {
    let modalDiv;

    function init() {
        const div = document.createElement('div');
        div.classList.add('modal-container');
        div.style.display = 'none';
        div.innerHTML = '<span>メッセージ内容</span>';
        document.body.appendChild(div);
        modalDiv = div;
    }

    return function() {
        if (!modalDiv) {
            init();
        }
        return modalDiv;
    };
})();

const myModal = createModal();
myModal.style.display = 'block';

3. モジュールパターン

モジュールパターンは、関数型プログラミングの原則を取り入れ、ローカル変数を private なフィールドとして公開する方法です。IIFE(Immediately Invoked Function Expression)を利用して、外部からの直接アクセスを防ぎつつ、必要なメソッドだけを exposed にします。


const AppModule = (function() {
    // プライベート変数
    let counter = 0;

    // プライベートメソッド
    function updateDisplay() {
        console.log(`Current value: ${counter}`);
    }

    return {
        increment: function() {
            counter++;
            updateDisplay();
        },
        reset: function() {
            counter = 0;
            updateDisplay();
        },
        get: function() {
            return counter;
        }
    };
})();

AppModule.increment();
// AppModule.counter は参照不可能です

4. プロキシパターン

プロキシパターンは、クライアントが実際の対象へのアクセスを仲介する代理オブジェクトを提供します。主な用途には、遅延生成(Virtual Proxy)、権限制御、およびキャッシュ提供があります。

4.1 仮想プロキシによる画像プリロード

重たいリソース(高解像度画像など)の読み込みを、実際の表示が必要なタイミングまで推迟することが可能です。


const ImageLoader = (function() {
    let imgNode;

    return {
        loadPlaceholder: () => {
            imgNode = document.createElement('img');
            imgNode.src = 'placeholder.gif';
            document.body.appendChild(imgNode);
        },
        setRealSrc: (src) => {
            imgNode.src = src;
        }
    };
})();

const VirtualImage = (function(target) {
    return {
        setSrc: function(src) {
            target.loadPlaceholder();
            
            // 実際のリクエストが発生した後で置き換える
            const tempImg = new Image();
            tempImg.onload = () => {
                target.setRealSrc(src);
            };
            tempImg.src = src;
        }
    };
})(ImageLoader);

4.2 キャッシュプロキシ

計算コストの高い処理の結果をキャッシュし、同一の入力に対しては即座に結果を返すことで性能を最適化できます。


function calculateExpensiveSum(...numbers) {
    return numbers.reduce((sum, num) => sum + num, 0);
}

const cacheProxy = function(fn) {
    const memo = {};
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (key in memo) {
            console.log('Hit from cache');
            return memo[key];
        }
        
        const result = fn(...args);
        memo[key] = result;
        return result;
    };
};

const memoizedCalc = cacheProxy(calculateExpensiveSum);

memoizedCalc(1, 2, 3);    // 計算を実行
memoizedCalc(1, 2, 3);    // キャッシュから即時返却

タグ: javascript design-patterns OOP coding-standards

7月5日 21:58 投稿