なるひこの Linux Printing お勉強日記 RSSフィード

ご注意!

ここはあくまでも私個人のブログであり、いかなる団体や組織の立場から離れた、私個人の知見なり学んだことなり勝手な思い(妄想ともいう)を書く場所であります。それを踏まえてお楽しみいただければ幸いです。

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

2015-01-30

デザパタを一人でこっそり振り返ろう #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パターンだそうです。お楽しみに。

2012-03-30

デザパタを一人でこっそり振り返ろう #4 (Factory Method)

なんと前の記事を確認したら一年以上前だよ…… 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 を答え合わせ的に読む、ということをやったら自分の勉強になるし、「動的言語におけるデザインパターンは静的言語のそれと違う」って意味が噛み締められるんじゃないかと思って始めた連載?です。

過去の記事は次のとおり*1

ということで今回は Factory Method パターン。JDP から引用すると(p.46)、

Template Method パターン (中略) をインスタンス生成の場面に適用したもの

なんだそーです。

リフレクションとかの機能がなくて、動的にクラスにメソッドを追加できなくて、文字列をもとにオブジェクトを操作するみたいな作業がニガテな C++Java に比べると、SmalltalkRuby なんかだとあんまり大げさなことやらなくてもいい気がするんですが、はてどうでしょう。


ひとまず実践

えーと、JDP で挙げられている例は、「IDカード」を抽象化したクラスを作るためのフレームワークを提供するってものです。入門書って性格上しょうがないと思うんですが、JDP の例はこういう「例のための例」が多くてちょっと歯がゆいです。

まあ文句をいってないで実装しましょう。

JDP では Java なのでパッケージ機能を使って、framework パッケージと idcard パッケージに分けてますが、これは Smalltalk ならカテゴリーにしましょうか。framework はクラスを「作る」「使う」という抽象概念を定義し、idcard はそれを具象化するパッケージというわけ。

まずは framework 側。カテゴリー名は当たらないように長ーくなってます。通常の Smalltalk の名前付けルールとは違うけど許して。

んで具象側のオブジェクト側はこんな感じ。

出力は手抜きで Transcript に出すので、Transcript を表示させておいて:

hoge := IDCardFactory new.
pro1 := hoge createOf: 'なるひこ'.
pro2 := hoge createOf: 'なるおが'.
pro3 := hoge createOf: 'naru0ga'.
pro1 use.
pro2 use.
pro3 use.

とかやると、

なるひこのカードを作ります。
なるおがのカードを作ります。
naru0gaのカードを作ります。
なるひこのカードを使います。
なるおがのカードを使います。
naru0gaのカードを使います。

てーな感じになるわけです。


super new って大丈夫なん?

で、これを書くときに意外とハマったのが、コンストラクタで値を初期化するよーな書き方。って、今までやったことなかったんかいな自分……。

具体的には、

IDCardFactory class>>new
	^ super new initialize.

IDCardFactory>>initialize
	owners := Bag new.

あたりですね。

C++JavaRuby なんかだとコンストラクタでメンバー変数代入すればいいんで (C++ だと初期化子を使うほうが行儀がいいのか?) 最初は:

IDCardFactory class>>new
	owners := Bag new.

って書いたら怒られた。「クラスインスタンス変数には owners なんてねーよ」って。おうふ。

じゃあってんでインスタンス生成のときに値を初期化しているようなオブジェクトなんかねーかなって考えて、そうだ Complex とかどうしてるんだろって見たら、

Complex class>>new
	^ self real: 0 imaginary: 0.

Complex class>>real: aNumber1 imaginary: aNumber2
	| newComplex |
	newComplex := super new.
	newComplex
		real: aNumber1;
		imaginary: aNumber2.
	^ newComplex.

みたいになってる。ここで real: と imaginary: はどっちも Complex で定義されてるメソッド

んー? super new したものに Complex のメッセージ投げて、なんでうまく動くんだろう?? と思って、ついったに:

って書いたら (ここで Hoge>> は Hoge class>> の間違い)、@abee さんに:

と教えてもらった。ふむ。

ということは、super new して得られるオブジェクトはあくまでも super の持つオブジェクト (= IDCardFactory クラス) のインスタンスオブジェクトなんだけど、new を処理するのはその上位 (= Factory -> Object -> ...) ってーことなんやね。なんかややこしいけど、イディオムとして覚えとこ。

まあ結果としては、今回の書き方で正しいらしいです。

このあとの議論も面白かったし、ここ深堀するといろいろ面白そうだけど、主題じゃないのでまたいつの日か。


考察的な何か

で、えーと、とりあえずこの例だと:

  • Factory の時点で「作るために必要なもの (Factory class>>createFor: anOwner の anOwner)」が決まってしまっているのは果たしてどうなのか。ものすごく使いにくいような気がする
  • なんで Register しているのか説明がなんにもないので意味がわかんない。これはこのパターンに必要なのか?

とか思っちゃうわけですよね。

後者のほうはどっちかというとテキストの問題ですな。IDCardFactory クラスで register するのは多分、作ったオブジェクトに対してブロードキャストとか処理の委譲をしたいからだと思うんだけど、JDP だとそういう話はぜんぜん出てこないし、そもそもそういうことしたいなら具象クラスじゃなくて抽象クラスの Factory の方でやらないとマズイ気がするし、教科書としては不要な例じゃないかなーと思うのだけど、まあ、先を読み進むと意味があるのかもしれませんし、そこはあまり追求しますまい。

むしろ前者が辛いですよねー。これって実用できる範囲をとても狭めてしまうと思うんだけど……そこのところ、もうちょっと解説が欲しいすな。例えばウィジェットの生成なんかは Factory をよく使う例だと思うんだけど、ウィジェットの場合、生成時に必要な初期化パラメータって微妙に異なったりしますよね。そういう場合、このパターンでやるのが正解なのか、違うパターンでやるのがいいのかとか、もし Factory Method でやるとしたら Java の場合にはどう実装するのが正解なのかってのが知りたいなーと。


DPSC を読んでみた

さて DPSC の Factory Method パターンは p.63 から。

前にも書いたけど DPSC の場合は「このパターンは Smalltalk のクラスライブラリの中で実際こう使われてるよ」って書いてあることで、ここで例に出されているのはズバリ、MVC (Model-View-Controller)。MVC の元祖は Smalltalk なんですよね。でまぁ、例として挙がってるのは複数の種類 (Text と Drawing) の Document を扱える Editor アプリケーション。これは Editor を Factory、Document を Product として考えれば実現できますよと。


Factory Method で MVC のクラスの管理コストを下げる

MVC の辛いところは、要素が増えてくるととにかくクラスの量が爆発的に増えることですよね。DPSC で挙げられているのは View と Controller の関係なのですが、基本的には View は「違う見え方」に対して一つずつクラスを作るのに対して、Controller は対応するだけ作る必要はなくて上位のビューと共通化できることも多いです (Smalltalk の例であれば View の BooleanWidgetView > ActionButton に対し、Controller は両方とも WidgetController でよい)。となると、両者の「どのビューはどのコントローラを使うか」っていう管理をしないといけないです。MVC の元祖的な Smalltalk-80 の場合は Dictionary で管理してたらしいですが、これはクラスが増えるたびに管理が大変。

ということでこの点について VisualWorks で使っているテクニックが紹介されてます。さっくり引用(p.68)。

View>>getController
  "レシーバーの現在のコントローラーを返す。もしレシーバーのコントローラーが nil だったら
  (通常の場合)、レシーバーのデフォルトコントローラーの初期化されたインスタンスが導入され、
  そのインスタンスを返す。"
  controller == nil
    ifTrue: [self setController: self defaultController].
  ^controller.

んで、defaultController を Factory Method としてみなせば、それぞれの View に対応して defaultController の定義をこんなふうに書けばよくなる。

View>>defaultController
  ^Controller new.

Text>>defaultController
  ^TextController new.

しかしもうひと工夫。どちらの defaultController も「あるクラスに対して new を投げてるだけ」なんだから、これも括り出しちゃえばいいじゃないかと。そんなわけでこんな感じ。

View>>defaultController
  ^defaultControllerClass new.

View>>defaultControllerClass
  "レシーバーのデフォルトコントローラーのクラスを返す。"
  "もし Controller 以外のコントローラを使いたければ、サブクラスは"
  "defaultControllerClass を再定義すること。"
  ^Controller.

Text>>defaultControllerClass
  ^TextController.

……正直、きれいになったとは思うけど、うれしさがさっきの例とくらべてよくわからない自分がいます……。

あ、でも下位クラスでオーパーライドすべきメソッド (逆にいえば、同じコントローラで構わなければオーバーライドしなくてもいい) がわかりやすいのはいいですね。

ここでの defaultControllerClass みたいなメソッドは Constant Method と呼ばれるらしくて Kent Beck の:

に書かれているそうな。読んでみようかなー。

それから、同様の方法はクラスインスタンス変数を用いてもできますよねという話。さっきの例を書き換えればこんな感じかな?

View class instanceVariableNames: 'defaultControllerClass'.

View>>defaultController
  ^self class defaultControllerClass new.

View class>>initialize
  defaultContorllerClass := Controller.

TextView class>>initialize
  defaultContorllerClass := TextController.

あと p.73 からの「Known Smalltalk Uses」で、豊富な実例が紹介されていてなかなか素敵なのだけども省略。クラス生成だけじゃなくて「外部 (external)」のなにかを呼び出すときに、呼び出すパラメータ文字列を生成する奴なんかも Factory Method パターンで扱えるよってのはふむふむだった。


まとめ

さっきの疑問「インスタンス生成のときに決まった引数を渡さなきゃいけないのって、なんかうれしさがわからない」という件、DPSC では「ただ new するだけ (引数とか取らない) でも嬉しいパターンはたくさんある」ということを提示していて、これなら便利さが朧気ながら分かりますね。MVC の例のように、「あるクラスと関係するクラスを new して関連を保持しておく」みたいな処理はみんなこのパターンになるわけだから。むしろ「工場と製品」アナロジーから離れたほうが広く応用が考えられそうですね。

次回は Singleton。いつになるやらわかりませんが(^^;

*1:タイトルが一貫してないことが発覚したのでこそこそ修正。

2011-03-09

デザパタを一人でこっそり振り返ろう #3 (Template Method)

この企画は「動的言語においてはデザパタの多くは意味を成さないか違うパターンになる」というよくある俗説?を体感したくて、自分にとってはNative LanguageじゃないSmalltalkで結城先生のJavaデザパタ本 (以下JDP) をなぞってみるという企画でございます。

さて今日のお題からJDPとしては第2部に入りまして「サブクラスにまかせる」というもの。

んで最初がTemplate Methodパターンと。ふむ。

これって要は「親クラスの外部インターフェースを定義している処理を、より詳細にしたメソッドの単純な呼び出しになるよう分割して、その下の具象クラスに実際の処理を隠蔽する」って奴ですよね。


まずは実践

つーことで、JDPの例を、なるべくJDPを見ないようにSmalltalkで書くと*1、まずは AbstractDisplay は:

んで CharDisplay、StringDisplay*2 はこんな感じかな。

あー分かってます分かってます。String>>open と String>>close の実装が愚直すぎることは。コンストラクタで string が与えられたときにサイズ決まるんで、そのときに文字列作って持っておけばいいんだよね。ちょこちょこ試した感じだと、WriteStream 使えばいいのかなって思うんだけど。こんなふうに。

stream := WriteStream with: '+'.
('hogehoge' size) timesRepeat: [stream nextPut: $-].
stream nextPut: $+.
stream contents. "print it."

まあ、それはいいとして。

例によってワークスペースとトランスクリプト出して、

a := CharDisplay with: 'A'.
a display.

b := StringDisplay with: 'hogehoge'.
b display.

ってやって全選択して do it すると、

<<AAAAA>>
+--------+
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
+--------+

まぁ、これで一応、やるべきことはできた……けど、なんだか当たり前だのクラッカーだなぁ。デザパタってのはそゆもんっていえばそゆもんだけど。


とゆことで DPSC へ

では DPSC ではどんなことが書いてあるかというと、ちと見てみましょう。


継承って難しいよね実は?

OOPにおいては継承って意外と難しい概念なんで、SELF って言語だと継承をとっぱらっちゃって、代わりに delegate (前回出てきた「委譲」ですな) という概念を導入しちゃってますよとか何とか。

私のへっぽこ英語でさらっと読んだ範囲と自分の経験からいうと、継承が混乱を生むのはその上位下位の関係でなにを抽象化するのかがごちゃごちゃになるからであって*3、「この継承関係はこういうことを抽象化するの!」ときちんとポリシーさえあれば、やっぱり継承は便利だよねと。

で、Template Method はまさにそういう「ポリシーをきちんと決める」例であって。


abstract なメッセージをどう実現するか

えっと、ここは前回からのちょっとした進歩。

私の Smalltalk はまだぴよぴよなので、前回のエントリでは、Java でいう abstract、C++ でいう pure virtual なメッセージの実装方法がわかんなかったわけ。で、コメントだけ書いて定義空っぽにしておいたのね。

でも、ここではまだ実装しないよ、サブクラスの責務だよ、というときには Object>>subclassResponsibility ってメッセージを self に投げとくと、よきに計らってくれるんだそうです。つか、「サブクラスでオーバーライドしないとダメだよん」って実行時エラー出してくれるだけだけど。

でも、実は前回のやり方も間違いってわけじゃなくて、「別に実装しないんだったら、なにもしないでもいいよ」というインターフェースってのもあるでしょうと。そゆ場合には空っぽの実装おいておけばいいよと。

これはあれだよね、Java だったら abstract にしないで stub にしとけばいいとかそういう話だよね。


クラス階層設計の原則

えーと、Auer って人がクラス階層を定義するんだったらこんなふうにしなさいよって指針を出してるそうです。詳しくは DPSC p.359 を読んでください。一応メモ。

  1. クラスは振る舞いによって定義しなさい。状態によって定義してはいけません。
    つまり最初はクラスの構造 (平たくいえば属性;インスタンス変数とか) を考えちゃダメだよと。
  2. 振る舞いを抽象化された状態を使って実装しなさい。
    逆の言い方をすれば、抽象的な振る舞いを実装するのに必要な状態 (属性) だけを考えて、後のことは後でかんがえろということなんだろね。
  3. メッセージのレイヤを認識しなさい。
    つまり親クラスで実現されるべき振る舞いを、サブクラスによって実装されるべきいくつかの小さなメッセージを呼び出す形で考えとけってことかしら。
  4. 状態変数の特定を後回しにしなさい。
    んっとこれは、親クラスでは処理の多くをサブクラスにお任せするわけだから、その処理に必要な状態を親クラスが持つべきではないよねと。サブクラスでそれぞれの処理を実装するときに、じゃあどういう状態 (属性;インスタンス変数) が必要かを考えなさいよと。

まぁ、おっしゃるとおりで。このとおりにやってれば、Template Method って形に自然になるよねーと。


あと Delegate (委譲) について「なにを委譲するの」って説明とか*4、クラス階層のリファクタリングのケーススタディとかあって、面白いんだけど省略して。


めんどくさいメソッドを Template Method で実装する方法

というのが書いてあったのでサラッと紹介。詳しくは DPSC 363-364 を。

  1. 最初はまず実装しちゃえ
  2. それをいくつかのステップに細分化しよう
  3. それぞれのステップをメソッド化しよう
  4. そのメソッドを呼ぶように書き換えよう。
  5. メソッドの中にクラス名やリテラルやその他定数になるものがあったら、「定数を取り出すメソッド (constant method)」に分割しよう
  6. 1〜5を繰り返そう。

多分5が一番ミソっぽいよな。ここで constant method をうまく抽出できれば、それを下位のクラスに追い出すことができるわけだから。

あと Smalltalk で Template Method パターンがどう使われてるかってのが延々続くわけだけど、そしてそれがとても面白いのだけど、私が力尽きたので省略。つかみんな DPSC 買って読め。


まとめ

正直 Template Method はあんまり Smalltalk らしさがないんで、前回の Adapter パターンほどワクワクしなかったんだけど、クラス階層設計のポリシーとか Template Method の抽出のしかたとかの丁寧な説明を読んで、DPSC のテキストとしての良さを再認識した気がします。

JDP だとパターンの説明は分かりやすいんだけど、「ほらね、継承使えば便利なことがあるってことがわかったでしょ。でも、どういうふうに継承を使うかってのは決まりはないんだよねー」とか説明が腰が引けてるんだけど、それって DPSC に比べるとズルいと思う。いや、対象読者層が違うといえばそうなんだけど、さ。

単なる公式集じゃなくて、そこからもう一歩上がった「この公式ってどういう考え方で出てくるの?」ということをちゃんと書いてるのが好感度。ここらへん GoF はどうだったっけかなぁ。


さてお次は Factory Method パターンか……これも楽しみですな。しかし、いつ書けるのやら。

*1:最近ムダにgist使うのがお気に入り。

*2:どうでもいいが、Char と String で名前の縮め方のルールが違うのが気になる……。

*3:私の見た酷いコードの例だと、あるクラスの責務が外部仕様の変更によって大きく変わったときに、それを継承によって表現してて (つまり親が I/F なのはいいとして、子が「旧世代のクラス」「新世代のクラス」で兄弟になってる)、新世代のクラスで I/F が変わっていくと、それに合わせて親を変えなきゃいけなくなって、親の I/F が変わるということはそのサブクラスも I/F を合わせなきゃいけないわけだから、もう使われるはずがない旧世代のクラスもせっせこ stub 書かなきゃいけないという最悪のがあった。

*4:これがけっこう面白いのだけど、紹介は機会があれば。

2011-03-03

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

シリーズ第2回。今回はAdapterパターン*1

まぁ平たくいっちゃえば「オブジェクト間のインターフェースをコンバートしてやることで、いろんなオブジェクトを統一的に扱うことができるようにしようね」ってもの。まあ「アダプター」だもんね。

インターフェースを使う側を Client、アダプターインターフェースを提供するものを Target、Target の定義するインタフェースを橋渡しするのが Adapter、Adapter から呼ばれて機能を提供するものを Adaptee というようです。


Tailored Pattern

GoF /JDP で説明しているパターンがこれ。

二通りの実装があるよって書いてある。ぐぐれば出てきそうなもんなのでクラス図書いて説明したりはしないけど。

継承方式というのは、Target を抽象クラスにしておいて、Target と Adaptee を多重継承して、Adaptee で定義されているインタフェースを self で使って Adapter がインタフェースを実現するというやり方。

委譲方式というのは、Adapter は具象クラスで、その代わりに内部に Adaptee のインスタンスを持って、そのインスタンスにメッセージを送ることでインターフェースを実現する方法。Adaptee のインスタンスに「この処理お願いっ」ってするから「委譲」っていうんだね。


ということは、「Adapter が、Target に提供すべきインターフェースと、Adaptee の提供するインターフェースをがっちり知っていなければならない」ですよね。

ということで、DPSC ではこれを「Tailored Adapter」と呼んでます。「仕立てアダプター」っつか「オーダーメイドアダプター」とでも言うのか。


んでDPSCでは「Smalltalkには多重継承ないから、委譲でしかできないんだよね」ってことで GoF の例を実装してるけど、せっかくだから JDP の例をやってみませうか。

でも JDP の例ってあんまよくなくて、モノが一個しかないのになんでインターフェースを併せてあげなきゃいけないの? って思うよね。ココらへんはLineとTextという違うインタフェースを持つものを統一的に扱いたいから……っていうGoF/DPSCの例のほうが分かりやすいとは思うけど、動かして結果が見やすいからこういう例なのかな?

まあさておき、ぼくが Smalltalk で愚直に実装するとなるとこうかな。

Banner クラスはこんなんで、

Print クラスはすごくシンプル。

PrintBanner クラスはこんな感じかなぁ。

トランスクリプトとワークスペース開いてワークスペースに以下のコードを書いて。

testBanner := Banner has: 'Hello'.
testBanner showWithAsters.
testBanner showWithParens.

Transcript cr.

test := PrintBanner with: testBanner.
test printStrong.
test printWeak.

うりゃっと全選択して do it すればトランスクリプトに、

*Hello*
(Hello)

*Hello*
(Hello)

されますねと。


Traits 使ってみよう

Traits というのは Ruby のモジュール mix-in に近いけど、名前がぶつかったときにエイリアスで逃したりできる、もっと緩い感じのモノ、という理解でいいのかな。

Matz さんが Ruby にも入れたいなーと言っているらしいところを見ると、mix-in とは違ったメリットがあるんでしょう。多分。

Squeak の派生である処理系 Pharo と、Squeak 3.9*2 から入りました。

これを使えば継承方式で実装できるのかなーなんて思った。

ということでさささっと書いてみたっす。

まずは Print を Trait として定義するわけだけど、Smalltalk 的には Trait には T という Prefix を付けるようなので、名前を変えまして。

んで、PrintBanner の定義はこんなふうになるのかな。名前は継承版と被らないように一応変えておいた。

使い方は委譲方式とちょい変わって、

test := PrintBanner2 has: 'Hello'.
test printStrong.
test printWeak.

で、ちゃんと実行できました。ぱちぱち。


そもそも Tailored Adapter っているの?

ぼくがこの説明を JDP で読んで、最初に思ったのは、

なにもこんな大げさなコトやらなくても、

Banner>>printStrong
	self showWithAsters.

Banner>>printWeak
	self showWithParens.

でよくね?

ということだった。

DPSC にもその点については書いてあって、「C++ だったらリコンパイルがいるからこういうやり方はちょっとないけど、Smalltalk だったらやってダメな法はないよねー」って書いてあった。やっぱそうか、そうなんか。

多分 ruby でもこんな感じに書けるんじゃないかな。

class Banner
    def printStrong
        self.showWithAsters
    end
end

Message-Based Pluggable Adapter

さて Tailored Adapter は「呼ぶ方のも呼ばれる方のもインターフェースが明らか」という仮定をおいていた。でも、この仮定が崩れたとしたら?

つまり、設計ができた「あとから」新しい Adaptee ができるとしたら?

例えば MVC パターンでウィジェットが表示用にモデルから値を取り出すときなんかを考えると、ビューのウィジェットが Client でモデルが Adaptee になって、クライアント側は value メッセージで表示用の値をとって value: メッセージでセットするとかなる。ところがモデルになるクラスって自前で作るとは限らなくて他所からの流用だったりライブラリだったりするわけで、そうなるとそれごとにAdapter書かなきゃいけない。そういう種類が違うモデルが出現するたびにこんなに大騒ぎするのめんどくさいですよねえ。

めんどくさいのもそうなんだけど、クラスってのはオブジェクトの「雛形」なんであって、Adaptee の数だけ雛形が存在するのって、ちょっとなんだかなぁ、って思いません? ふつー思いますよね。

そこで Smalltalk で活躍するのがこの Message-Based Pluggable Adapter なのだ。


要はね、Adaptee が新たに登場するたびに Adapter クラスを作るのって大変じゃないですか。

そこで、インスタンスに対して動的に動作を突っ込める Smalltalk の機能を活用して、「Adapter クラスのこのインスタンスはこの Adaptee 用」ってできれば嬉しいじゃないってこと。だとぼくは理解してる。

例えばさっき例に出した MVC の話で言うと、ビューがモデルから値を取る value ってメッセージと、逆にモデルに値を設定する value: ってメッセージが考えられるじゃないですか。で、こいつらの動きを、インスタンスごとに定義できたらクラスが爆発しないでいいよねと。

DPSC の写経をしてもしょうがないので大雑把な構造を書くと、

  • Adapter クラスはコンストラクタで Adaptee のインスタンスを貰っておく。これは以上による Tailored Adaptor と同じですね。
  • Smalltalk には "perform:" ってメッセージがあって、以下の二つは等価なんです。
anObject #hogehoge
= anObject perform: #hogehoge
  • ここで重要なのは、#hogehoge が変数に代入されたものであっても構わないということ。だから例えば getSelector という変数に束縛されているとしたら、以下のように書けるということ。
anObject perform: getSelector
  • ということは selector メッセージを使えば、一般的な Adapter というクラスのインスタンスオブジェクトに対して「value に対するメッセージは何?」という設定ができるってことになるわけです。

例を挙げましょう。細かいクラス定義は省略するとして、あ、anObject と printStrongSelector は MessageAdapter のインスタンス変数ね。

と定義しておけば、

adapter := MessageAdapter on: (Banner has: 'Hello').
adapter setPrintStrong: #showWithAsters.
adapter printStrong. " => *Hello*"

と実行できますよと。おお、すげー。

さらに文字列をシンボルに変えるメッセージ asSymbol を使えば、シンボル一個渡すだけで、getter / setter ど同時に定義できたりする。

この仕組みを使うことで、クラス数の爆発を抑えつつ、MVC なんかでの M と V の結合を柔軟に変動させることができる。うーん、これぞプログラミングの醍醐味という奴ではないかい?

この仕組みはめちゃんこクールだと思う。詳しくは DPSC p.114-115 を読むとよいですよ。


Parameterized Adapter

Message-Based Pluggable Adapter はとてもクールなんだけど、インタフェースがパラメータを要求するとき、それを渡すすべがないという問題? がある。

ということでそんな場合に使うのが Parameterized Adapter。

すごい平たくいってしまうと「getter / setter にブロックを渡してしまう」って Adaper。GoF や JDP における Adapter パターンとははるか遠くに来てしまいましたが、モダンなプログラミング言語なら似たようなことはできると思う。


あまりまとまってないまとめ。

  • GoF や JPD で紹介されている Tailored Adapter は Smalltalk (あるいは他のモダンな言語) でも実装できるけど、追加すべきメソッドが静的に決まるのであれば、既存クラスを直接いぢっちゃても (動的言語の場合) 問題ないので、正直誰得。
  • その点、インスタンスごとにあるオブジェクトのメッセージをどうディスパッチするかを指定できる Message-Based Pluggable Adapter はすごい強力。
    • ということで、Pluggable Adapter は VisualWorks でもりもり使われてる、そうな。

ここで紹介したことがすべてではないので、動的言語においてデザインパターンっってどうあるべきなのかってことに興味がある人は、ぜひ

Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

を買って読んでみてくだされ。ぼくですら読めるので、英語であることはまったく障害にならないと信じます。


さて次はなんだっけ。Template Method だっけ。

DPSC 的にはどんな切り口なのか楽しみですね。

*1:なお Iterator と Adapter っていう順番なのは JDP が「この二つはとっても分かりやすいから」ということで選んだらしくて、Iterator については DPSC にも「コレクションのなかのオブジェクトに繰り返し処理を行うことはSmalltalkの再利用のもっとも分かりやすい例だよね」って書いてある。

*2:最初 4 系と書いてましたが、コメント欄で umejava さんから教えていただきました。多謝多謝。

2011-02-26

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

2011.02.27 注記:umejava さんのコメントにより、一部間違った記述を訂正。


最初は読書会に参加して勉強させて貰うつもりだったんですが、本来の趣旨であるところの:

GoF を基礎から丁寧に振り返りましょう

というテーマを無視してヨタ話に突っ走り、それはそれで非常に面白かったんだけど、むしろ基礎を押さえたい人を置いてけぼりにしてしまったことにいたく反省をして、読書会は抜けたわけで。

でも、そのためにせっかく買った

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

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

がもったいないやん! ということになり、抜けた読書会と同じペースで振り返ろうかな、とか思ってたらすっかりサボってしまいました。

そしたら注文してた、

Design Patterns Smalltalk Companion, The (Software Patterns Series)

Design Patterns Smalltalk Companion, The (Software Patterns Series)

が届いたので、眺めたら面白いのなんのって。


つーことで、以下さらさらっと舐めたいと思います。

以下結城先生の本は JDP、Smalltalk のヤツは DPSC と略記します。


Iterator パターン

d:id:naruoga:20110210:1297363662 で振り返った内容なわけですが、DPSC の見解は Ruby などの動的言語でも通用するんじゃないかな。


内部イテレータ

「ある固まり (Aggregator) に対して、その要素すべてにある処理を順に実行していく」という、その「要素すべてに」順次実行していく、という制御そのものを内部に隠蔽してしまうのが内部イテレータ。

  • Smalltalk の場合 Collection クラスだったら内部イテレータを容易してるから、それ使えばおけ。具体的には do: で実行ブロックを渡すというありがちなやり方。
  • 逆に言えば Collection のサブクラスは do: を適切に実装しなきゃダメ。
    • 木構造のような再帰的データ構造の場合は do: の定義を再帰でやれば簡単に内部イテレータは実現できる。
  • イテレータ回してる最中に要素を削除したり追加したりすると悲しくなるので、そういうことしたいなら、あらかじめ copy メッセージ送ってコンテナをまるっとコピーしておけと*1

外部イテレータ

内部イテレータの方が記述がコンパクトだけど、処理を外部から制御したいときだってある。そういう場合は「進む」「戻る」という概念を外に出して、積極的に制御を外部に取り出してやる。これが外部イテレータ。

  • Smalltalk の場合には Collection のサブクラスである Stream が外部イテレータとして使える。
    • ←2011.02.17 追記:Stream は内部イテレータである do: メッセージもサポートしていますが、クラス階層としては Collection のサブクラスにいるわけではありません。
    • non-Smalltalker 的には、Stream はファイルなんかの抽象概念ですよ、といえばいいのか。C# なんかにもあったっけ。
  • Smalltalk では String は Character の Stream なので、レキサとか書くのに便利だよ。
  • テキストエディタのアンドゥ・リドゥを実現したいときなんかにも Stream を使うといいよ。

ここまで丁寧に説明して貰うと、「内部イテレータだけでいくない?」という疑問がちょっと解消されるよね。


今 DPSC の Adaptor パターンを読み中なんだけど、こっちもすっげぇ面白い。早く追いつきたいよう。


でも今日はこれでおしまい!

ごめんなさい。

*1:空間効率いかにも悪そうだけど、オブジェクトを Copy On Write しとけば済む話なのかな。←2011.02.17 追記:Shallow Copyなのでそれほど重くないそうです。やっぱそうですよね。