GWT MVPあれこれ
Place, Activity, View, Presenter を使った開発が段々分かってきた。
以下一連の流れ。だいたいは http://code.google.com/intl/ja/webtoolkit/doc/trunk/DevGuideMvpActivitiesAndPlaces.html ここの通り。
- まずclient.viewパッケージ以下にFooViewインターフェースを作り、取り敢えず空のPresenterインターフェースとsetPresenterメソッドだけ定義する。
- GWT DesignerのUiBinderなどを使ってFooViewImplクラスを作り、FooViewをimplementsする。
- それからclient.placeパッケージ以下にPlaceを継承したFoo(FooPlace)を作りインナークラスでPlaceTokenizer
を実装したTokenizerを定義する。TokenizerのgetPlaceでreturn new Foo()し、getTokenは空文字でも返しておく。 - ビューが出来たので、FooViewに対応するFooActivityをclient.activityパッケージ以下に作る。これはFooView.Presenterを実装してコンストラクタでClientFactoryを受け取り、startメソッドでお決まりのコード(※)だけ書く。
- 最後にAppActivityMapper#getActivityに処理を追加してAppPlaceHistoryMapperのアノテーションにTokenizerを追加すると、空のページ表示までが完成。
※
private FooView view; @Override public void start(AcceptsOneWidget panel, EventBus eventBus){ view = clientFactory.getFooView(); view.setPresenter(this); panel.setWidget(view); }
あとはUiBinderでビューを作っていって、onClickなどの処理はpresenter(Activity)に委譲する。また、presenterでRPCなどを実行したあとに結果を設定する場合は、上のコードで書いたように保持しておいたviewのsetResultなどを呼び出し結果を設定する。
一つのビューでtokenを使って状態を管理したい場合は、FooActivityのコンストラクタにFoo(FooPlace)を渡してFoo#getTokenの内容によってstartメソッドの初期化を切り替える。
たぶんこんな感じ。
View、View.Presenter、ViewImpl、Activityは一蓮托生のようになっていて、一応疎結合なんだけどもワンセットになる。FooActivityがFooView.Presenterを実装しているので、Activityの状態によってビューを切り替えるということは出来ない。この場合はActivity、Placeもビューごとに作った方が良い。
または、ActivityはView.Presenterを継承せずに委譲するようにしたら切り替えが出来るかな。どっちがいいんだろう。
Placeの中でViewを表示する前に、何らかの状態によって別のPlaceに切り替えるということもやめた方が良さそう。
例えばViewを持たないCheckPlaceを作り、ログイン状態によってMemberPlaceとGuestPlaceに切り替えるっていう方法はイマイチ。ブラウザバックが効かなくなってCheckPlace以前の履歴に戻れなくなる。
じゃぁどうすればいいのかというと…今のところ考え中。何にせよPlaceから別のPlaceに転送したらブラウザバックが効かなくなるので、権限のないPlaceに来たらエラー用Viewを表示するのがいいのかな。まぁ正規の手順でブラウジングしてたら権限のないPlaceには来ることはないのでブラウザバックを諦めるという考え方もあるか。
GUIプログラミングの経験がほとんどないから知らないだけで、実はセオリーがあるのかも。
なんだかファイルが多くなっただけで現状ではメリットを感じない。clientのテストをまだ書いてないからかな。
A key concept of MVP development is that a view is defined by an interface. This allows multiple view implementations based on client characteristics (such as mobile vs. desktop) and also facilitates lightweight unit testing by avoiding the time-consuming GWTTestCase.
テストが楽って書いてあるしね。あとViewの切り替えも楽って書いてある?
あぁ…つまり、会員ページと非会員ページを切り替えるんじゃなくて、全ての機能が同じだけれど表示が異なるパソコンとiPhoneの表示を切り替えるのが容易で確実になるってことか。
それと、完全に別ページを切り替えるんじゃなくて、例えばネットショップならログイン済みユーザーのカート表示と非ログインユーザーのカート表示を切り替えるっていうことならできるのか。インターフェースの設計が難しそうだねぇ…。
GWT.reate(AppPlaceHistoryMapper.class)で生成したPlaceHistoryMapperは、Placeとtokenの区切り文字は : に固定されている。
実体は com.google.gwt.place.rebind.PlaceHistoryMapperGeneratorから作られる com.google.gwt.place.impl.AbstractPlaceHistoryMapper にあるようだ。
GWT.createによってPlaceHistoryMapperGenerator#writeGetPrefixAndTokenが呼び出され、ここで動的にメソッドを生成している模様。
区切り文字を変えたければ、自分でPlaceHistoryMapperをimplementsしたクラスを作る必要がある?
結構な量なので自分で作るのはやめたいが、区切り文字のコロンはAbstractPlaceHistoryMapperにハードコードされているので現状どうしようもなさそう。
PlaceHistoryMapperインターフェースにはgetPlaceメソッドとgetTokenメソッドしか定義されていないので、これをimplementsして自分で区切り文字を解析してPlaceをnewするクラスを作るのが早いのかもしれない。これだと自由にtokenとPlaceの対応付けができる。ただclient側のコードではClass#getSimpleNameやClass#getCanonicalNameが使えなかった。
あと、tokenの名前がよくないね。
PlaceHistoryMapper#getTokenのtokenはClassName:tokenのことで、Place#getTokenはコロン以降の文字だけを指すみたい。