【TS】今さら聞けないシングルトンパターン
はじめに
今回はシングルトンパターン(Singleton Pattern
)について解説します。
インスタンスの唯一性を保証する、GoF
の23種のデザインパターンの中でも有名なデザインです。
シングルトンパターンとは?
有名どころでTECHSCORE
さんの解説があります。
以下、引用です。
あるクラスのインスタンスが一つしかないことを保証したい場合があります。注意深く設計すれば、唯一のインスタンスを使いまわすことは可能でしょうが、このインスタンスが唯一であることを保障するものとはなりません。このような場合に、威力を発揮するのが Singleton パターンです。
Singleton パターンは、コンストラクタを private とすることで、他クラスから新たにインスタンスが生成されないような構造とすることで、インスタンスの生成を制御します。まずは、サンプルケースを考えて見ましょう。
続いてサンプルケース。
サンプルケースでは、図書館の貸出帳を考えて見ます。図書の貸出に当たって、その貸出帳がいくつあるのかわからないような状態では、、管理がとても難しくなります。例えば、「ゼロからはじめるJava」は貸し出し中ですか?と質問されたとき、貸出帳が何冊あるのか分からなければ、「この貸出帳によると、現在図書館にあるはずですが、貸出帳は他にもあるかもしれないし・・・」なんてことになりかねません。こんなとき「貸出帳はこの1冊のみ」と保証されていれば、本が貸し出し中であるのか、図書館にあるのかすぐに判断できます。
ポイントは、あくまで「保証」ということです。
この例だと、シングルトンパターンを用いずとも貸出帳の機能で排他制御をしっかりと行なっていれば、貸出内容の齟齬は防げるかもしれません。
ただ、そういったバグを起こさないように「設計レベルでインスタンスの唯一性を保証」するのがシングルトンパターンです。
例題
以下のような内容のクラスを想定します
-
NormalUser
とAdminUser
という2種のクラスを定義する - それぞれ
IUser
というインターフェースを実装する -
IUser
はdispInfo()
という関数を定義する -
NormalUser
はコンストラクタの引数でユーザ名を受け取る -
NormalUser
はdispInfo()
で自身のユーザ名を表示する -
AdminUser
はdispInfo()
で管理ユーザである旨を表示する -
AdminUser
インスタンスは唯一とする
ポイントは「AdminUser
のインスタンスが唯一」かつ「IUser
を実装している」という点です。
インスタンスを作りたくないだけなら、private constructor
としてインスタンスを生成できないようにした上で、static
でdispInfo
を宣言すれば実装できます。
class AdminUser {
// コンストラクタをprivateで宣言することで
// インスタンスを生成できなくする
private constructor() {}
static dispInfo = () => {
console.log('Admin User');
}
}
AdminUser.dispInfo(); // --> Admin User
俗に言う「ユーティリティクラス」です。
しかし、例のようにインターフェースを実装したい場合はdispInfo
をstatic
にすることができません。
シングルトンパターンで実装
シングルトンパターンで実装した例が下記になります。
AdminUser
は_instance
を持っており、instance
のゲッターで返却しています。
外部からはinstance
を経由してアクセスすることしかできず、結果としてインスタンスの唯一性が保証されています。
// ユーザIF
interface IUser {
dispInfo: () => void;
}
// 通常ユーザ
class NormalUser implements IUser {
private _name: string;
constructor(name: string) {
this._name = name;
}
dispInfo = () => {
console.log("UserName is ", this._name);
}
}
// 管理者ユーザ
class AdminUser implements IUser {
private static _instance: AdminUser;
private constructor() {}
public static get instance(): AdminUser {
// instanceがなければ生成
if(!this._instance) {
console.log("Generate AdminUser...");
this._instance = new AdminUser();
}
// 自身が持つインスタンスを返す
return this._instance;
}
dispInfo = () => {
console.log('Admin User');
}
}
const user = new NormalUser("taro");
const admin = AdminUser.instance; // --> Generate AdminUser...
user.dispInfo(); // --> UserName is taro
admin.dispInfo(); // --> Admin User
// 同じインスタンスを呼び出しているため「Generate AdminUser...」が表示されない
const admin2 = AdminUser.instance;
まとめ
今回はTypescript
でデザインパターンの一つである Singleton Pattern
について紹介しました。
途中にもあるようにシングルトンパターンは「インターフェースの実装」を考えなければ「ユーティリティクラス」として賄えてしまいます。
個人的には「インスタンスの唯一性を保ったまま、インターフェースを継承する」場合において有効なデザインパターンかなと思います。
ただ、このあたりは自分自身の理解が曖昧で、他にも「こういう理由で使った方がいい」とか「こういう場合もシングルトンが適している」等あるかと思います。
そういった場合はコメントで指摘いただけると幸いです。
Discussion
こんな話題がありました。
もしかしたら参考になるかもしれません。
コメントありがとうございます。
参考にさせていただきます!