言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Dolphin Smalltalk で遊ぶ


http://www.object-arts.com/content/navigation/products/dce.html

と、言うわけで、Model-View-Presenter で評判の Dolphin Smalltalk で遊んでみる。と言っても始めて触るのでさっぱりどこから手をつけていいやら。。。

メモ

  • 最初のウインドウを System Folder と呼ぶ
  • アプリケーションは Package Browser というので管理する。
  • メソッドは複数のカテゴリに登録できる。
  • 画面は View Composer で設計する。
  • 設計した画面は Presenter のサブクラスのクラスメソッド resource_Default_view に格納される。

準備

自分の作ったプログラムが他と混ざらないように Package を作ります。Package は Squeak の Change Set のような物です。System Folder から Package Browser を開けて、適当にパッケージを作り、右クリックで Set as Default Package とすると次から更新が新しいパッケージに記録されます。

華氏摂氏変換器の設計

例によって華氏摂氏変換器を作って Model-View-Presenter がどういう物なのか調べてみる。多少冗長だけど、設計は次のとおり。

  • モデル FCConverter はインスタンス変数 celsius を持つ。
    • アクセッサ celsius と celsius: は摂氏の温度を書いたり読んだりする。
    • アクセッサ fahrenheitfahrenheit: 変換して華氏の温度を書いたり読んだりする。
  • プレゼンター FCConverterPresenter は celsiusPresenter と fahrenheitPresenter を持つ。
    • それぞれ摂氏と華氏の温度を表示する。
    • 摂氏を入力したら華氏が、華氏を入力したら摂氏がそれぞれ更新される。

華氏摂氏変換器 Model の実装

クラス定義はこんな感じ

Model subclass: #FCConverter
	instanceVariableNames: 'celsius'
	classVariableNames: ''
	poolDictionaries: ''
	classInstanceVariableNames: ''

celsius のアクセッサー trigger: というのは、値が変わったことを周りに教える役目があります。

celsius
	^ celsius

celsius: aNumber
	celsius := aNumber.
	self trigger: #celsiusChanged.

fahrenheit のアクセッサー

fahrenheit
	^ self celsius * 1.8  + 32

fahrenheit: aNumber
	self celsius: aNumber - 32 / 1.8

初期化子

initialize
	celsius := 0

テストをクラスメソッドとして書いておく。

runTest
	"self runTest"
	| c |
	c := FCConverter new.
	c fahrenheit: 86.
	self assert: [c celsius = 30].
	c := FCConverter new.
	c celsius:40.
	self assert: [c fahrenheit = 104]

華氏摂氏変換器 Presenter の実装

Presenter とは耳慣れない言葉ですが、View と Model の仲立ちをします。

クラス定義

Shell subclass: #FCConverterPresenter
	instanceVariableNames: 'celsiusPresenter fahrenheitPresenter'
	classVariableNames: ''
	poolDictionaries: ''
	classInstanceVariableNames: ''

初期化子には createComponents というメソッドを使い sub-presenters を生成します。name: で指定する名前は、あとで View を作る時に対応していないといけません。sub-presenters というのは、摂氏、華氏それぞれの表示を管理する子オブジェクトです。

createComponents
	super createComponents.
	celsiusPresenter := self add: NumberPresenter new name: 'celsius'.
	fahrenheitPresenter := self add: NumberPresenter new name: 'fahrenheit'.


model: でモデルを決定する時それぞれに sub-model を割り当てます。sub-model は aspectValue: で作られる ValueAspectAdaptor オブジェクトで、典型的には親モデルのそれぞれの状態を監視する物です。今回モデルのインスタンス変数は celsius fahrenheit という二つのアクセッサを持つので、それぞれに ValueAspectAdaptor を作成します。

それから aspectTriggers: で、sub-presenters がモデルの更新通知を受け取るようにします。

model: aFCConverter
	super model: aFCConverter.
	celsiusPresenter model: (aFCConverter aspectValue: #celsius).
	fahrenheitPresenter model: (aFCConverter aspectValue: #fahrenheit).
	celsiusPresenter model aspectTriggers: #celsiusChanged.
	fahrenheitPresenter model aspectTriggers: #celsiusChanged

礼儀として標準のモデルを設定します(クラス側)。

defaultModel
	^ FCConverter new

華氏摂氏変換器 View の実装

Model と Presenter の定義が終わったら、ツールを使って View を定義します。

  • ブラウザの FCConverterPresenter 右クリック Views - New - Resource name 'Default vew' のままで OK
  • View Toolbox- TextField - TextPresenter.Default view を二回ドロップ
  • それぞれ name プロパティを celsius, fahrenheit に変更
  • ShellView のプロパティ - usePreferredExtent を好みで false に
  • 好みでキャプションをつけたりする。
  • File - Save で FCConverterPresenter class >> resource_Default_view に保存される。
  • 実行は FCConverterPresenter show です。

更新の詳細

上記で aspectTriggers: を使って省略した更新の詳細について調べます。aspectTriggers: の実装には when:send:to: が使われています。これとtrigger: と組み合わせて更新通知に使います。単純な例を作ってみました。

model := Object new.
counter := 0.
model when: #fire: send: #value: to: [:aNumber | counter := counter + aNumber ].
model trigger: #fire: with: 7.
Transcript print: counter; cr.

意地悪して、イベントハンドラの中でさらに trigger: するとあっけなく無限ループになりました。そうするとなぜ摂氏華氏変換器の例で、無限ループにならないのかが謎です。

Aspect

あるオブジェクトのインスペクタを開くと、最初に Published Aspects という項目が表示されます。aspect はオブジェクトの特に重要な項目だけ表示したい時に使います。詳しくは PublishedAspectInspector のクラスコメントに書いてあります。例えば

FCConverter class >> publishedAspectsOfInstances
    
    	^(super publishedAspectsOfInstances)
    		add: (Aspect integer: #fahrenheit );
    		add: (Aspect integer: #celsius );
    		yourself.


のようにすると、華氏のインスタンス変数が無いにも関わらず摂氏と華氏両方の値がインスペクタに表示されて便利です。これは最高に面白い機能です。

MVP とは何か?

横道に逸れましたがもう一度 MVP とは何かを Gtk と比較します。

  • Model : GObject と同じ。依存元になれる物。
  • View : Gdk オブジェクト群 + GtkWidget の描画メソッド。表示に関する物。
  • Presenter : GtkWidget の描画メソッド以外。画面の論理構成に関する物。

結論。描画をどこでやるかが違うだけ。ウィジェットごとに別のクラスを用意しないといけないので、Gtk の方が好きだなー。今までの個人的ランキング。Gtk > MVP > Swing