no versions found for ApplicationFrameworkReferences / Utility/singleton / en

Singleton

モジュールパス: /alier_sys/Singleton.js

概要


Singleton クラスは、シングルトンパターンのクラスを実装するための基底クラスとして利用します。

シングルトンパターン
  • シングルトンパターンとは、インスタンスが1つだけであるようなクラスの設計パターンのことです。

Singleton クラス自体はシングルトンパターンを実装するクラスではありませんが、アプリケーション上でクラスのインスタンスがただ一つに限られるようなクラスの作成を支援します。具体的には、以下の手順で作成されたクラスは単一のインスタンスだけを持ちます:

  1. Singleton を継承する
  2. コンストラクタの中で initialize() メソッドを呼び出し、その返値をコンストラクタから return する
    • initialize() メソッドは引数 initializer として関数を受け取ります。引数として渡した関数はクラスインスタンスの初期化のために使います。 initialize() メソッドは、クラスインスタンスが未初期化の場合に限り、与えられた initializer を呼び出して初期化します。

実装例は以下の通りです:

//  1.  Singleton を継承したクラスとして MySingleton を定義します。
class MySingleton extends Singleton {
    constructor() {
        //  JavaScript の言語仕様上、super() の記述は必須です。
        //  また super() が値を返すまで、this を参照することはできません。
        super();

        //  2.  this を対象として initialize() を呼び出し、その結果を return します。
        //      initialize() の引数の関数は、MySingleton のインスタンスが未初期化の場合にのみ、
        //      呼び出されます。
        //      initialize() は常に同一の初期化済みの MySingleton インスタンスを返します。
        //      コンストラクタからの return は new MySingleton の結果として返ります。
        return this.initialize(() => {
            //  以下に初期化に必要な処理を書きます。
            this.x = 42;
            this.y = "foo";
        });
    }
}

実装した MySingleton を利用する場合は、単に new 演算子を使ってインスタンスを取得します。

const mySingleton = new MySingleton();

こうして定義された MySingleton のコンストラクタは常に同じインスタンスを返します。 従って、例えば new MySingleton() === new MySingleton() は常に true を与えます。

この項目での言葉の使い方
  • 曖昧さをなくすため、本項およびその関連するページにおいては原則的に、フレームワークが提供するクラスを「Singleton クラス」と表記し、デザインパターンとしては「シングルトンパターン」と表記します。
    • Singleton を継承したクラスを 「Singleton 派生クラス」と表記します。
    • Singleton 派生クラスのうち、実際にコンストラクタが常に同一のインスタンスを返すクラスを「シングルトンパターンを実装する Singleton 派生クラス」と表記します。
    • Singleton クラスのインスタンスを「Singleton インスタンス」と表記します。
Singleton 派生クラスの継承について
  • シングルトンパターンを実装する Singleton 派生クラスを継承して、新たな別のクラスを作ってはいけません。 基底側のクラスのインスタンスが常に同一であることを保証できなくなるためです。
  • 一方で、シングルトンパターンを実装しない Singleton 派生クラスは自由に継承できます。 基底側のクラスがインスタンスが同一であることを保証しないため、派生クラスがインスタンスを作っても矛盾が生じないためです。

コンストラクタ


構文

new Singleton()

解説

Singleton クラスのインスタンスを生成します。

Singleton(またはシングルトンパターンを実装していない Singleton 派生クラス)のコンストラクタからインスタンスを取得する場合、それは常に新しいインスタンスを生成します。常に同一のインスタンスを得るには、そのクラスがシングルトンパターンを実装する Singleton 派生クラスとして作られていなければなりません。

シングルトンパターンを実装する Singleton 派生クラスを作るには、そのクラスのコンストラクタから initialize() を呼び出して、その結果を return しなければなりません。

class MySingleton extends Singleton {
    constructor() {
        super();
        return this.initialize(() => {
            /* ここでオブジェクトを初期化します */
        });
    }
}

以下ではファイル RemotePreferences.js でシングルトンパターンを実装する Singleton 派生クラスとして RemotePreferences クラスを実装します(ただしインポートおよびエクスポートの記述などは省略しています)。

class RemotePreferences extends Singleton {
    constructor() {
        super();

        return this.initialize(() => {
            this.#theme        = "polarized-light";
            this.#muteSince    = "21:00";
            this.#muteUntil    = "09:00";
            this.#lastModified = "1970-01-01T00:00:00Z";
        });
    }

    #theme;
    #muteSince;
    #muteUntil;
    #lastModified;

    get() {
        return {
            theme       : this.#theme,
            muteSince   : this.#muteSince,
            muteUntil   : this.#muteUntil,
            lastModified: this.#lastModified
        };
    }

    async update(preferences) {
        const {
            theme      : newTheme,
            muteSince  : newMuteSince,
            muteUntil  : newMuteUntil
        } = preferemces ?? {};

        const {
            theme       : oldTheme,
            muteSince   : oldMuteSince,
            muteUntil   : oldMuteUntil
        } = this.get();

        if (newTheme != null && newTheme !== oldTheme) {
            this.#theme     = newTheme;
        }
        if (newMuteSince != null && newMuteSince !== oldMuteSince) {
            this.#muteSince = newMuteSince;
        }
        if (newMuteUntil != null && newMuteUntil !== oldMuteUntil) {
            this.#muteUntil = newMuteUntil;
        }
        try {
            await this.sync();
        } catch(e) {
            this.#theme     = oldTheme;
            this.#muteSince = oldMuteSince;
            this.#muteUntil = oldMuteUntil;
            throw e;
        }
    }

    async sync() { /* リモートサーバとの同期処理が実装されています */ }
}

ファイル MyApp.jsRemotePreferences を利用するには以下のようにします(以下でクラス MyApp の詳細などは省略しています):

class MyApp extends ViewLogic {
    async messageHandler(msg) {
        msg.deliver({
            setTheme: async (msg) => {
                const { theme } = msg.param && {};
                const pref = new RemotePreferences();
                await pref.update({ theme });
            },
            setMute: async (msg) => {
                const { muteSince, muteUntil } = msg.param && {};
                const pref = new RemotePreferences();
                await pref.update({ muteSince, muteUntil });
            },
            disableMute: async (msg) => {
                const  ;
                const pref = new RemotePreferences();
                await pref.update({ muteSince: "", muteUntil: "" });
            }
        });
    }
}

メソッド


initialize()

  • 対象の Singleton 派生クラスを初期化します。引数として関数を与えることで、追加の初期化処理を記述できます。