1. シングルトンパターン (Singleton)
たった一つのオブジェクトだけを生成して状態を共有するシングルトンパターンについて学びましょう。
ひとことで言うと
プログラム全体にちょうど1つだけあるべきオブジェクトをクラスが自分で預かり、どこからでも同じ入り口からだけ持っていくようにするパターンです。
問題: 1つであるべきなのに、いくつもできてしまうオブジェクト
アプリの設定を作るとしましょう。設定には画面で使う言語が入っています。画面も通知もこの設定を見ます。まず思いつくのは、設定が必要な場所ごとに設定オブジェクトを新しく作る方法です。
【設定】を設計しよう:
『言語』を【文字列】の「日本語」にしよう
(参考:画面と通知が、それぞれ設定を新しく作ります)
【画面】を設計しよう:
『設定』を【設定】の新しい【設定】()にしよう
〈言語を変える〉を作ろう(【文字列】の『新しい言語』):
『私』の『設定』の『言語』を『新しい言語』にしよう
〈表示する〉を作ろう():
枠「[画面] 今の言語: {『私』の『設定』の『言語』}」を出力しよう
【通知】を設計しよう:
『設定』を【設定】の新しい【設定】()にしよう
〈表示する〉を作ろう():
枠「[通知] 今の言語: {『私』の『設定』の『言語』}」を出力しよう
『最初の画面』を新しい【画面】()にしよう
『通知欄』を新しい【通知】()にしよう
『最初の画面』の〈言語を変える〉(「English」)を実行しよう
『最初の画面』の〈表示する〉()を実行しよう
『通知欄』の〈表示する〉()を実行しよう
実行結果
[画面] 今の言語: English
[通知] 今の言語: 日本語
言語を英語に変えたのに、通知はまだ日本語です。バグです。
- 設定が2つできました。
【画面】と【通知】がそれぞれ新しい【設定】()をしたので、名前だけ同じ別々の設定オブジェクトを1つずつ持っています。片方を変えても、もう片方はそのままです。 - 使う場所が増えるほど食い違います。 設定を使う場所が10か所なら、設定も10個です。どれが「本当の設定」なのか、だれにも分かりません。
- エラーになりません。 プログラムは問題なく動き、結果だけが静かに間違っています。だから見つけにくいです。
解決策: 1つだけ作り、同じ入り口からだけ持っていく
シングルトンパターンは、「1つだけでなければならない」というルールをクラスが自分で守るようにします。
- クラスが自分のオブジェクトを1つ、静的メンバー(
『私たち』の)として預かっておきます。 最初は空っぽです。 - オブジェクトが必要なら、だれでも
〈取得する〉という同じ入り口を通ります。最初に呼ばれた時に1つ作って預かり、その後は預かっているそのオブジェクトを返します。 - だれかが
新しい【設定】()でこっそりもう1つ作ろうとしたら、作られる瞬間にエラーを出して止めます。
たとえ: 学校の職員室の鍵
職員室の鍵は1本だけで、守衛室に預けてあります。先生たちは必要な時に守衛室で受け取り、ドアはいつもその1本の鍵で開けます。先生ごとに合鍵を作って持っていったら、だれがドアを閉めたのか、鍵が何本あるのか、だれにも分からなくなりますよね。守衛室こそが〈取得する〉です。
だれが何をするの?
| 役割 | この例では | やること |
|---|---|---|
| シングルトン | 【設定】 |
自分のオブジェクトを1つ預かり、もう1つ作られるのを止めます |
| 預かっているオブジェクト | 『私たち』の『_ひとつ』 |
プログラム全体で一緒に使う、たった1つの設定です |
| たった1つの入り口 | 【設定】の〈取得する〉() |
最初は作り、その後は同じものを返します |
| 使う場所 | 【画面】、【通知】 |
設定が必要になるたびに、同じ入り口から持っていきます |
カナデで作る
同じアプリをシングルトンパターンで作り直してみましょう。最後には、こっそりもう1つ作ってみる場面も入れました。
(参考:ステップ1 — たった1つの設定を、クラスが自分で預かっておきます)
【設定】を設計しよう:
『私たち』の『_ひとつ』を空っぽに隠そう
『言語』を【文字列】の「日本語」にしよう
(参考:ステップ2 — もう1つあれば、また作れないように止めます)
最初に作られる時()次のようにしよう:
もし(『私たち』の『_ひとつ』が空っぽと違う)なら:
新しい【エラー】(「設定は1つだけでなければなりません。【設定】の〈取得する〉()を使ってください。」)を発生させよう
(参考:ステップ3 — どこからでも、この入り口からだけ持っていきます)
【設定】を返す『私たち』の〈取得する〉を作ろう():
もし(『私たち』の『_ひとつ』が空っぽと同じだ)なら:
『私たち』の『_ひとつ』を新しい【設定】()にしよう
『私たち』の『_ひとつ』を返そう
【画面】を設計しよう:
〈言語を変える〉を作ろう(【文字列】の『新しい言語』):
【設定】の〈取得する〉()の『言語』を『新しい言語』にしよう
〈表示する〉を作ろう():
枠「[画面] 今の言語: {【設定】の〈取得する〉()の『言語』}」を出力しよう
【通知】を設計しよう:
〈表示する〉を作ろう():
枠「[通知] 今の言語: {【設定】の〈取得する〉()の『言語』}」を出力しよう
『最初の画面』を新しい【画面】()にしよう
『通知欄』を新しい【通知】()にしよう
『最初の画面』の〈言語を変える〉(「English」)を実行しよう
『最初の画面』の〈表示する〉()を実行しよう
『通知欄』の〈表示する〉()を実行しよう
とりあえずやってみよう:
『こっそり作った設定』を新しい【設定】()にしよう
発生したら(『エラー』):
(『エラー』の『メッセージ』)を出力しよう
実行結果
[画面] 今の言語: English
[通知] 今の言語: English
設定は1つだけでなければなりません。【設定】の〈取得する〉()を使ってください。
今度は、画面で変えた言語が通知にも表示されます。2つとも同じ1つの設定を見ているからです。そして、もう1つ作ろうとするとその場で止められます。
ステップごとにもう一度
- ステップ1 — クラスが自分で預かる。
『私たち』の『_ひとつ』は、オブジェクトごとに別々にできる値ではなく、【設定】クラスに1つだけある値です。隠そうで作ったので、外から直接は変えられません。 - ステップ2 — 2回目の誕生を止める。 コンストラクタは、
『_ひとつ』がもう埋まっていればエラーを出します。最初に〈取得する〉が呼ぶ時はまだ空っぽなので無事に作られ、その後はだれが新しい【設定】()をしても止められます。 - ステップ3 — たった1つの入り口。
〈取得する〉は最初の1回だけオブジェクトを作ります。必要になった時に初めて作るこのやり方を遅延初期化と言います。設定を一度も使わないプログラムなら、設定を作りもしません。
カナデではコンストラクタを隠せないの?
はい、カナデにはコンストラクタを隠そうにする方法がありません。そのため、このページのようにコンストラクタの中で確かめて「1つだけ」を守ります。おかげで、うっかり新しい【設定】()と書いても静かに2つできてしまうことはなく、何を使えばいいかを教えてくれるエラーになります。
シングルトンの影、そしてもっと簡単な方法
シングルトンは便利ですが、使いすぎると困ったことになるパターンとしても有名です。
- どこからでも変えられる値になります。
【設定】の〈取得する〉()はプログラムのどこからでも呼べるので、だれがいつ言語を変えたのか追いかけにくくなります。 - 隠れたつながりができます。
【通知】のコードを見ただけでは、設定を使っているかどうか分かりません。中に【設定】の〈取得する〉()が隠れているからです。 - 別々に試しにくくなります。 1つしかないので、「英語の設定の時の通知」と「日本語の設定の時の通知」を1つのプログラムで並べて試すのが難しいです。
そこで多くの場合、1つだけ作って、必要な場所に渡してあげる方法のほうがすっきりします。プログラムのいちばん最初に設定を1つ作り、画面と通知が作られる時にその設定を渡すのです。
【設定】を設計しよう:
『言語』を【文字列】の「日本語」にしよう
(参考:設定を自分で作らず、作られる時に受け取ります)
【画面】を設計しよう:
『設定』を【設定】の空っぽに隠そう
最初に作られる時(【設定】の『設定』)次のようにしよう:
『私』の『設定』を『設定』にしよう
〈言語を変える〉を作ろう(【文字列】の『新しい言語』):
『私』の『設定』の『言語』を『新しい言語』にしよう
〈表示する〉を作ろう():
枠「[画面] 今の言語: {『私』の『設定』の『言語』}」を出力しよう
【通知】を設計しよう:
『設定』を【設定】の空っぽに隠そう
最初に作られる時(【設定】の『設定』)次のようにしよう:
『私』の『設定』を『設定』にしよう
〈表示する〉を作ろう():
枠「[通知] 今の言語: {『私』の『設定』の『言語』}」を出力しよう
(参考:プログラムのいちばん最初に1つだけ作って、配ります)
『共通の設定』を新しい【設定】()にしよう
『最初の画面』を新しい【画面】(『共通の設定』)にしよう
『通知欄』を新しい【通知】(『共通の設定』)にしよう
『最初の画面』の〈言語を変える〉(「English」)を実行しよう
『最初の画面』の〈表示する〉()を実行しよう
『通知欄』の〈表示する〉()を実行しよう
実行結果
[画面] 今の言語: English
[通知] 今の言語: English
結果はシングルトンと同じです。でも今度は、【通知】が「私は作られる時に設定を受け取ります」と外に見せています。 試す時は、別の設定を作って渡すだけです。
どっちを使う?
渡す場所がいくつもないなら、渡すやり方のほうが読みやすく、直しやすいです。プログラムのとても深い場所まで渡さなければならず、すべてのコンストラクタに設定がくっつくのがかえって面倒なら、その時にシングルトンを使いましょう。
どんな時に使うといいの?
- プログラム全体に本当に1つだけあるべきものがある時(設定、記録帳、1つの接続を分け合う管理者)
- その1つをいくつもの場所で一緒に使う必要があるのに、いちいち渡すのが難しい時
- 作るのに時間がかかるので、必要な時に1回だけ作りたい時
いいところと気になるところ
| いいところ | 気になるところ |
|---|---|
| どこから持っていっても同じオブジェクトなので、値が食い違いません | どこからでも変えられるので、だれが変えたのか追いかけにくいです |
| うっかり2つ目を作ると、すぐに教えてくれます | 使う側のコードにつながりが隠れています |
| 必要な時に最初の1回だけ作ります | 1つしかないので、別の値に差し替えて試しにくいです |
早すぎるパターンは毒です。
「今は1つしか使わないから」はシングルトンを使う理由になりません。1つだけ作って渡せば十分な場合のほうがずっと多いです。2つ以上できると間違いになる時、そして渡すのが本当に面倒な時だけシングルトンを使いましょう。
ほかのパターンとの関係
- ファクトリーパターンと一緒に使えます。 工場が設定などの値を覚えておく必要があるなら、工場を1つシングルトンにして、みんながその工場に頼むようにします。
- ファサードパターンも1つあれば十分なことが多いです。 複雑な機能のまとまりの前に立てる窓口はふつう1つなので、シングルトンにすることがあります。
- オブザーバーパターンとよく出会います。 プログラム全体の知らせ(ログイン、設定の変更)を出すオブジェクトをシングルトンにして、いろいろな場所から購読させます。