読者です 読者をやめる 読者になる 読者になる

おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

デザパタを一人でこっそり振り返ろう #5 (Singleton)

なんと前の記事を確認したら3年近く前だよ…… orz

この連載?は、へっぽこプログラマー(厳密には足を洗ったので「元」)のぼくがひょんなきっかけから、Javaプログラマー向けデザインパターンの入門書として有名な:

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

(以下 JDP)を買ったはいいけどなんかやる気なくて放置してたところに、これまた Smalltalk 界隈の人に教えてもらった:

Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

(以下DPSC)を買って読んだらむちゃくちゃ面白くて、じゃあ JDP の内容を Smalltalk で実装したあと、DPSC を答え合わせ的に読む、ということをやったら自分の勉強になるし、「動的言語におけるデザインパターンは静的言語のそれと違う」って意味が噛み締められるんじゃないかと思って始めたものです。

過去の記事は次のとおり。

ということで今回は Singleton パターン。言わずもがなではありますが、JDP から引用すると(p.58)、

指定したクラスのインスタンスが絶対に1個しかないことを保証したい

というものですな。これまた同じページから引用すると:

現在のシステム設定を表現したクラス、ウィンドウシステムを表現したいクラスなどが代表的な例

だそうです。ウィンドウシステムって、どのウィンドウもどっかからちゃんと手繰れないといけないんだけど(でないと、例えばマウスクリックとかのイベントを誰に渡せばいいんだかわかんなくなる)、そういうときに「全部のウィンドウの根っこ」とか持ちたくなるわけですね。そういうのをシングルトンで管理するよと。

そんなにインスタンスの作成を抑制したいんだったら、全部クラスメソッドでさばいてインスタンス作んなければいいんじゃね?と思うんですが、まあよくわかんないので教科書を追います。

ひとまず実装

JDP の例を見てみましょう。シングルトンであるクラス Singleton を作ります。

平たく言ってしまえば new を禁止して、代わりに「まだインスタンスがなければ new したものを、そうでなければすでにあるインスタンスを返す」メソッド getInstance を提供するってな感じです。Java の場合は new を禁止するにはコンストラクタ Singleton を private にすればいいわけですが、さて Smalltalk だとどうするか。private なんて Smalltalk にはねーよ。

まあ new 禁止を考えずにクラスの定義をば。


super new については前回 #4 で説明したとおり。ここで Singleton new ってやっちゃうと怒られる。……いや、これ self basicNew のほうがいいね。あとで気づいた。

でも自身の new 潰してないんだよなあ。ということで new にカーソル当てて alt-M でインプリメンタ出してなんかいいのないかなーと見てると、お、こんなんあった。

Bool class>>new
self error: 'You may not create any more Booleans - this is two-valued logic'

ふむ。self に error: で適切なエラーメッセージを投げてやればいいのね。じゃあこんなん?

そうすると「new 潰すと酷いことになるかも知んないけどマジいいの?」って聞かれるけどまあ構わずセーブ。

さてとワークスペース開けて試してみましょう。

a := Singleton getInstance.
b := Singleton getInstance.
a == b[print it]→true

ほい。うまくいったっぽい。

DPSC の解説

さて答え合わせ答え合わせ。DPSC の Singleton パターンは 91 ページから。ふむ。大筋は合ってるっぽい。new は self>>error: で潰せというのも正解でした。

Smalltalk 的にはシングルトンそのもの、あるいはシングルトンっぽいものはたくさん使われてるよって書いてあります。例えば Squeak の場合は Smalltalk 変数というのがいてコイツがグローバルな色々な何かを持ってたりしますが、このクラスである SmalltalkImage というクラスは new は「Smalltalk 使えよ」ってエラーを出すように潰してあります。

ということで、大抵のシングルトンパターンの場合は、システムそれ自体のグローバル変数とかに持っててそれ経由のアクセスをするのが普通なわけです。例えば上の例で示した Smalltalk 変数とかね。で、コイツの初期化はシステムのブートアップにやりますよと。でもシングルトンのオブジェクトってそれこそシステム根幹の情報なわけで、それをグローバル変数に持つってのはどうなのよ? 危なそうに見えるよね? と。

で、答えとしては、グローバル変数じゃなくてシングルトンオブジェクトのクラスに一つのメッセージを用意して、そいつからインスタンス変数にルーティングすればいいじゃんって。オブジェクトが生成されたとき、初期化されたとき、GCによって破棄された時など、クラスなら適切に扱えるじゃんねーと。なるほどね。でも実際のところ Smalltalk の大抵のシングルトンオブジェクトはグローバル変数で管理されてますよと。なんでやねん。

ちょっと英語苦手なんで実は意味読み取れてないんだけど引用:

Design Patterns states that Singletons accessed through global variables are not really example of the Singleton pettern (DP 127). One might argue that other examples are Singletons and they're just not implemented optimally.

デザインパターンではグローバル変数経由でアクセスされるシングルトンを Singleton パターン (DP 127) の実際の例とはしていません。一つ考えられることは、他の例はシングルトンなのですが、最適な実装がされていなかったということです。

つまるところ GoF ではシングルトンオブジェクトをグローバル変数に突っ込むのを「パターン」ではないとしてるけど、それっぽい例があるから「おーこうやって実装するといいねー」って最初実装しちゃったって話? そうなんかな。まあいいや。あ、ここで DP 127 っていうのはいわゆる Gang of Four (GoF) のデザインパターン

のページ 127 にあるパターンって意味です。はい。

とにかく本当はグローバル変数に突っ込むのは良いやり方ではないけど、Smalltalk ではそういう例がいっぱいあるってことはわかった。

シングルトンのバリエーション

シングルトンには persistent (永続)、transient (一時的)、single-active-instance (単独アクティブインスタンス) の三つのバリエーションがあるとのこと。

  • 永続シングルトン - 特定のクラスの唯一のインスタンスであり、なおかつ永遠に変わらないもの。
  • 一時的シングルトン - あるクラスの唯一のインスタンスだけども、インスタンスは変わりうるもの。例えばセッション情報を管理するオブジェクトはシングルトンであるが、セッションが破棄されると破棄され、新規にセッションが起きると再生成される。
  • 単独アクティブインスタンス - あるクラスに対し、アクティブまたは有効であるインスタンスは一つだけだが、インスタンス自体は複数存在するもの。例えば IDE のプロジェクトを管理するクラスがあるとして、IDE複数のプロジェクトをオープンできるのでインスタンス複数存在するけど、実際にプログラマーが弄ってるプロジェクトに対応するプロジェクトだけが有効とかそういう。実際にはシングルトンではないけど、性質的には似てるから一緒に論じますよと。
予約語

Smalltalknil, true, false とかは実は UndefinedObject, True, False という「クラス」のシングルトンオブジェクトとみなせますよと。実際 true をインスペクトすると True ってクラスのオブジェクトだよって言われるしね。

Smalltalk における実装について

Smalltalk 内部で実際に使ってるケースを紹介してくれるところが DPSC のうれしいところ。
GoF では Singleton パターンの要件として「(A)あるクラスがただひとつのインスタンスしか持たないこと」だけでなく、「(B)グローバルにアクセスできる方法を提供すること」を挙げているらしいです。ん? JDPにはその要件はスルーされていたような……。まあいいや。

単一インスタンスの保証

で、(A)はさっき議論したように、Smalltalkの場合はクラス変数 (Java の場合は静的メンバー変数っていうのかな)に作ったインスタンス放り込んでおいて new 禁止ってやり方でいいよねと。で、ついでにこんなふうな実装もありうるねと。これは Visual Smalltalk の UndefinedObject クラス。

UndefinedObject>>new
"レシーバーの新たなインスタンスを作る。このクラスでは単一のインスタンス nil が
存在するため許可されない。"
^self invalidMessage

で、invalidMessage を別途定義することで再利用性を上げると。ただ、VisualWorks とか Squeak とかこういうふうにしてなくて素朴に self>>error: 投げてる処理系も多いよと。

C++とかJavaの場合はnewをプライベートにするだけでできることなんだけど、すでにあるクラスを隠すって概念がないSmalltalkの場合は実行時例外を上げるしか方法がなく、文法レベルでチェックができないのは欠点だよねってことが書いてありました。まあそれは一理あるわなー。

インスタンスへのアクセスの提供

(B) についてですけど、さっきも書いたとおりグローバル変数を使うやり方は乱暴ではあるけど一応機能はする。もっと良いやり方はクラスでプロキシーする方法で、getInstance って名前はいかにも「インスタンスを貰う(貰った側が代入して持つ)」って名前でこれはちょっとよろしくない。ので、名前をcurrentとかに変えれば:

Singleton current someMessage.

みたいに自然に書けるでしょ? というお話でした。

new を潰さない方法ってどう?

よくよく考えてみると、newを潰す必要は別になくて、単にこうすればいいんじゃないかと。

Singleton class>>new
^self current

上の議論により self current はシングルトンオブジェクトを(必要なら内部で new して)返すんだから、これによって常に new で同じものが帰るよねと。

問題は、new って名前が「いかにもインスタンスを生成しそう」って名前なので:

| roadRunner wileECoyote |
roadRunner := SingleToon new.
wileECoyote := SingleToon new.
roadRunner position: 100@100.
wileECoyote position: 200@200.

で、実は roadRunner と wileECoyote が同じオブジェクトだとはお釈迦様でも気づくめえってことですわね。ので、やっぱ new は潰しておくほうが無難かなと。プログラマーが混乱したんじゃ意味ないんで。

クラス階層の中でのシングルトン

「このクラスのサブクラスの中のインスタンスは一個しかあっちゃダメ」みたいなことが欲しいときはどうするかって話ですね。Smalltalk の場合は一瞬で、クラス変数の代わりにクラスインスタンス変数を使えばいいですよと。つまり:

Object subclass: #Singleton
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'DPStudy-Singleton'

Singleton class instanceVariableNames 'Singleton'

とやれば後は同じでいいですね。っていうかクラスインスタンス変数ってこんなふうに使うんだー。

おっと、単なるテキストの引き写しになってる。いかんいかん。
その後も DPSC は「アクセサの名前 current とか default とかってどう決める?」とか、ぼくが最初に書いた「全部クラスメソッドでよくね?」とかそういう話があって、もっと具体的なコード例(データベースのラッパークラス)とかSmalltalkクラスライブラリの中での使用例とかいろいろ書いてあるわけでこれもまためちゃくちゃ面白い。が省略。買って読んで。

さて次回はいつになるかわかりませんがPrototypeパターンだそうです。お楽しみに。