MVP GWT 2.1の概要

コマンドテンプレートを使用してGWTでRPC呼び出しを整理することに関するトピックを書いたとき、GWTプロジェクトのアーキテクチャを構築するためのMVPパターンについて言及しました。 同志Ray RayanがGWTでの複雑なアプリケーションの設計に関するプレゼンテーションを行った直後、世界中の労働者が声明のあるアイデアをライブラリとフレームワークの形で実装し始めました。 これらの作業の結果は、MVPアプローチのいくつかの側面( GWT-Presenter )とその全体( Mvp4G )の両方を適用する手段となりました。 これはすべて素晴らしいですが、私は個人的に(私は他のGWT開発者も確信します)、MVPスキームに従ってGWTアプリケーションを編成するための標準化されたフレームワーク/アプローチ(もしそうなら)を持ちたいと思っています。 そして今、GoogleのGWTを担当するチームは、最終的にバージョン2.1で、残りのおいしいものとともに、組み込みのMVPフレームワークを提供しました。

この記事では、組み込みのGWT MVPフレームワークの主要なポイントを強調します。 例として、このアプローチを使用して構築された小さなアプリケーションが使用されます。

この記事はフリートラクト( GWT MVP Development )であり、決してユニークなふりをするものではありません。 誰かがこのようなかなり大きな紹介に興味を持っていた場合



MVPの設計パターン自体に焦点を合わせないことをすぐに言います。 たとえば、( WikiのMVP )でそのスキームに慣れることができます。 %username%に興味を持たせるために、最終的にはアプリケーションのスクリーンショットを提供します。

デモプロジェクト

はい、アプリケーションは役に立たないことを理解していますが、それに基づいて、アーキテクチャを個別の独立した部分(メール、連絡先、タスク、ちなみに天井から取ったもの)に分割し、それらの間の切り替えと通信を整理する方法を示しますGWT 2.1に組み込まれたメカニズムを使用します。 さらに、MVPパターンのMコンポーネント、つまり これは、説明目的のアプリケーションにはデータバインディングがないためです。



組み込みMVPの主な機能



GWTチームは、MVPを使用してアプリケーションを構築するための次の主要コンポーネントを提案しました。



例として実際のコードを使用して、これらすべてのコンポーネントを個別に詳細に検討してみましょう。 ところで、アプリケーションコードは無料で利用できます 。 そのため、読みながらそれを覗くことができます。



ビジュアルコンポーネントまたはビュー



まず第一に、MVPでは、プレゼンターとビューの間のインターフェイスのみを「交換」するのが一般的です。 したがって、アプリケーションの各ビューには、対応するインターフェースがあります

package com.gshocklab.mvp.client.mvp.view; import com.google.gwt.user.client.ui.IsWidget; public interface IMailView extends IsWidget { public void setPresenter(IMailPresenter presenter); public interface IMailPresenter { } }
      
      





ビューのインターフェースは、すべてのペイロード(データの要求の送信、イベントバスからのイベントの処理など)を実行するプレゼンターの対応するインターフェースを記述します。

このインターフェイスの実装は単純であり、特別な問題を引き起こすことはありません。

 public class MailView extends Composite implements IMailView { interface MailViewUiBinder extends UiBinder<Widget, MailView> { } private static MailViewUiBinder uiBinder = GWT.create(MailViewUiBinder.class); private IMailPresenter presenter; public MailView() { initWidget(uiBinder.createAndBindUi(this)); } @Override public void setPresenter(IMailPresenter presenter) { this.presenter = presenter; } }
      
      





リンクされたui.xmlファイルには、プレーンテキスト付きのラベルが1つだけ含まれています。 彼のコードを持ってきても意味がありません;プロジェクトのウェブサイトで見ることができます。

ビューについては以上です。 次に、より興味深い、アクティビティに目を向けます。



ページ(ビュー)またはアクティビティのロジック



左側のデモアプリケーションには、リンクのあるナビゲーションバーがあります。 あなたは、このリンクをクリックすると、現在のリンクのためのモードや設定CSSスタイルを切り替えます。 このアクションを抽象親クラスAbstractMainActivityに行いました。これは、組み込みクラスAbstractActivityの子孫です。

 package com.gshocklab.mvp.client.mvp.activity; public abstract class AbstractMainActivity extends AbstractActivity { private static Map<String, Element> navLinks = new LinkedHashMap<String, Element>(); static { navLinks.put(AppConstants.MAIL_LINK_ID, DOM.getElementById(AppConstants.MAIL_LINK_ID)); navLinks.put(AppConstants.CONTACTS_LINK_ID, DOM.getElementById(AppConstants.CONTACTS_LINK_ID)); navLinks.put(AppConstants.TASKS_LINK_ID, DOM.getElementById(AppConstants.TASKS_LINK_ID)); } public void applyCurrentLinkStyle(String viewId) { for (String linkId : navLinks.keySet()) { final Element link = navLinks.get(linkId); if (link == null) continue; if (linkId.equals(viewId)) { link.addClassName("b-current"); } else { link.removeClassName("b-current"); } } } }
      
      





そして、特定のアクティビティの特定の実装

 package com.gshocklab.mvp.client.mvp.activity; public class MailActivity extends AbstractMainActivity implements IMailView.IMailPresenter { private ClientFactory clientFactory; public MailActivity(ClientFactory clientFactory) { this.clientFactory = clientFactory; } @Override public void start(AcceptsOneWidget container, EventBus eventBus) { applyCurrentLinkStyle(AppConstants.MAIL_LINK_ID); final IMailView view = clientFactory.getMailView(); view.setPresenter(this); container.setWidget(view.asWidget()); } }
      
      



eventBus){ package com.gshocklab.mvp.client.mvp.activity; public class MailActivity extends AbstractMainActivity implements IMailView.IMailPresenter { private ClientFactory clientFactory; public MailActivity(ClientFactory clientFactory) { this.clientFactory = clientFactory; } @Override public void start(AcceptsOneWidget container, EventBus eventBus) { applyCurrentLinkStyle(AppConstants.MAIL_LINK_ID); final IMailView view = clientFactory.getMailView(); view.setPresenter(this); container.setWidget(view.asWidget()); } }





仕組み:ActivityManagerはPlaceHistoryManagerからURL変更イベントを受け取ると、ActivityMapperを使用して目的のActivityインスタンスを作成し、start()メソッドを使用して開始します。 このメソッドのパラメーターの1つは、ビューウィジェットが置き換えられるコンテナーです。 ClientFactoryからビューを取得しますが、これは少し低くなります。 プレゼンターを受信したビューインスタンスに挿入し、ビューをウィジェットとして表示します。 はい。CSSルールもリンクに設定されており、現在のビューにつながります。 しかし、これは純粋に視覚的なデザインです。

ClientFactoryは、適切なオブジェクトを作成するシンプルなファクトリーです。 そのインターフェースは次のように説明されています

 public interface ClientFactory { public EventBus getEventBus(); public PlaceController getPlaceController(); public IMailView getMailView(); public IContactsView getContactsView(); public ITasksView getTasksView(); }
      
      





その実装は、「インテリジェンスとクイックウィット」に違いはありません。

 public class ClientFactoryImpl implements ClientFactory { private final EventBus eventBus = new SimpleEventBus(); private final PlaceController placeController = new PlaceController(eventBus); private final IMailView mailView = new MailView(); private final IContactsView contactsView = new ContactsView(); private final ITasksView tasksView = new TasksView(); @Override public EventBus getEventBus() { return eventBus; } @Override public PlaceController getPlaceController() { return placeController; } @Override public IMailView getMailView() { return mailView; } @Override public IContactsView getContactsView() { return contactsView; } @Override public ITasksView getTasksView() { return tasksView;} }
      
      



placeControllerと、 public class ClientFactoryImpl implements ClientFactory { private final EventBus eventBus = new SimpleEventBus(); private final PlaceController placeController = new PlaceController(eventBus); private final IMailView mailView = new MailView(); private final IContactsView contactsView = new ContactsView(); private final ITasksView tasksView = new TasksView(); @Override public EventBus getEventBus() { return eventBus; } @Override public PlaceController getPlaceController() { return placeController; } @Override public IMailView getMailView() { return mailView; } @Override public IContactsView getContactsView() { return contactsView; } @Override public ITasksView getTasksView() { return tasksView;} }



contactsViewと、 public class ClientFactoryImpl implements ClientFactory { private final EventBus eventBus = new SimpleEventBus(); private final PlaceController placeController = new PlaceController(eventBus); private final IMailView mailView = new MailView(); private final IContactsView contactsView = new ContactsView(); private final ITasksView tasksView = new TasksView(); @Override public EventBus getEventBus() { return eventBus; } @Override public PlaceController getPlaceController() { return placeController; } @Override public IMailView getMailView() { return mailView; } @Override public IContactsView getContactsView() { return contactsView; } @Override public ITasksView getTasksView() { return tasksView;} }





ClientFactoryオブジェクトのインスタンス化は、GWTモジュールの記述ファイルに記述されているルールに従って、Defferedバインディングを使用して実行されます。 ただし、このセクションの後半でさらに詳しく説明します。このセクションでは、単一の稼働システムでのMVP経済全体の構成について説明します。 ClientFactoryが解決するタスクの実際のプロジェクトでは、Google GINを使用することをお勧めします。 DIツールの利点を説明する意味はありません。それらはすでに理解可能です。

埋め込みMVPの最後の主要な要素は、UIの状態を管理し、履歴トークンを操作するオブジェクトです。



URLの配置またはハッシュとその処理



上記のように、PlaceオブジェクトはUIの現在の状態を担当します。 ステータスは、URL経由で履歴トークンを介して渡されます。 実際、このオブジェクトでは、ハッシュURLで送信されるパラメーターを保存できます。 ステータスURLエンコード/ Tokenizer'aオブジェクトを使用して復号化。 ハッシュURLで送信されるリクエストパラメータを使用する場合、次のルールに従うことが非常に重要です。処理後にURLから「入力」するすべてのパラメータは、同じ形式でURLにエンコードし直す必要があります。 Tokenizerクラスのメソッドに実装されるのはこのロジックです。

GWTチームによって受け入れられ、公式マニュアルで説明されている合意により、トークナイザークラスは通常、Placeオブジェクトの内部静的クラスとして説明されます。 これにより、Placeオブジェクトの変数にクエリパラメーターを格納するためのコードが簡素化されます。 ただし、トークンの個別のクラスでアプローチを適用できます。

根拠がないように、MailPlaceクラスのコードを検討します

 package com.gshocklab.mvp.client.mvp.place; import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceTokenizer; import com.google.gwt.place.shared.Prefix; public class MailPlace extends Place { private static final String VIEW_HISTORY_TOKEN = "mail"; public MailPlace() { } @Prefix(value = VIEW_HISTORY_TOKEN) public static class Tokenizer implements PlaceTokenizer<MailPlace> { @Override public MailPlace getPlace(String token) { return new MailPlace(); } @Override public String getToken(MailPlace place) { return ""; } } }
      
      



> { package com.gshocklab.mvp.client.mvp.place; import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceTokenizer; import com.google.gwt.place.shared.Prefix; public class MailPlace extends Place { private static final String VIEW_HISTORY_TOKEN = "mail"; public MailPlace() { } @Prefix(value = VIEW_HISTORY_TOKEN) public static class Tokenizer implements PlaceTokenizer<MailPlace> { @Override public MailPlace getPlace(String token) { return new MailPlace(); } @Override public String getToken(MailPlace place) { return ""; } } }





このクラスは、組み込みのPlaceクラスから継承されます。 その中で、状態を一意に識別するハッシュURLの定数部分が宣言されます。 この場合、それは「メール」です。 Tokenizerクラスは、状態を再構築し、履歴トークンを介して保存します。 特定のハッシュURLをバインドすると、アノテーションを使用してtokenayzeruする接頭辞

処理履歴は一般に興味深いトピックであり、別の記事に値します。 ここでは、各Placeオブジェクトに異なるハッシュURLが関連付けられるという事実に限定しています。 このURLは「:」で終わる必要があります。 このコロンの後、追加のパラメーターを指定できます。たとえば、#mail:inbox、#contacts:newなどの形式のURLを作成できます。 これらのトークンはgetPlace()メソッドで処理されます。 実際、ハッシュURLの最初の部分はサブシステム識別子(メール、タスクなど)であり、コロンの後に続くものはすべてアクションサブシステムと見なすことができます。

デモプロジェクトでは、追加のトークン(またはアクション)は使用されないため、getToken()メソッドはすべてのトークンで空の文字列を返し、getPlace()メソッドは作成されたPlaceオブジェクトを返します。



目的のアクティビティの決定とハンドラーの登録



新しいURLが到着してPlaceオブジェクトが正常にインスタンス化されると、ActivityMapperを使用して、ActivityManagerマネージャーが実行するプレゼンターオブジェクトを決定します。 この定義は簡単に実装されます

 public class DemoActivityMapper implements ActivityMapper { private ClientFactory clientFactory; public DemoActivityMapper(ClientFactory clientFactory) { super(); this.clientFactory = clientFactory; } @Override public Activity getActivity(Place place) { if (place instanceof MailPlace) { return new MailActivity(clientFactory); } else if (place instanceof ContactsPlace) { return new ContactsActivity(clientFactory); } else if (place instanceof TasksPlace) { return new TasksActivity(clientFactory); } return null; } }
      
      





ハッシュURLハンドラーの登録、つまり トークナイザーは、PlaceHistoryMapperインターフェースで実行されます

 package com.gshocklab.mvp.client.mvp; import com.google.gwt.place.shared.PlaceHistoryMapper; import com.google.gwt.place.shared.WithTokenizers; import com.gshocklab.mvp.client.mvp.place.ContactsPlace; import com.gshocklab.mvp.client.mvp.place.MailPlace; import com.gshocklab.mvp.client.mvp.place.TasksPlace; @WithTokenizers({MailPlace.Tokenizer.class, ContactsPlace.Tokenizer.class, TasksPlace.Tokenizer.class}) public interface DemoPlaceHistoryMapper extends PlaceHistoryMapper { }
      
      





この段階で必要なのは、@ WithTokenizersアノテーションでアプリケーショントークナイザークラスをリストするだけです。



すべてをまとめる



MVPフレームワークメカニズムを初期化および開始するためのすべてのコードは、EntryPointのonModuleLoad()メソッドでコンパイルされます。

 package com.gshocklab.mvp.client; import com.google.gwt.activity.shared.ActivityManager; import com.google.gwt.activity.shared.ActivityMapper; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.shared.EventBus; import com.google.gwt.place.shared.PlaceController; import com.google.gwt.place.shared.PlaceHistoryHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.gshocklab.mvp.client.layout.AppLayout; import com.gshocklab.mvp.client.mvp.DemoActivityMapper; import com.gshocklab.mvp.client.mvp.DemoPlaceHistoryMapper; import com.gshocklab.mvp.client.mvp.place.MailPlace; public class MvpInActionEntryPoint implements EntryPoint { private SimplePanel containerWidget; private MailPlace defaultPlace = new MailPlace(); @Override public void onModuleLoad() { final AppLayout mainLayout = new AppLayout(); containerWidget = mainLayout.getAppContentHolder(); final ClientFactory clientFactory = GWT.create(ClientFactory.class); EventBus eventBus = clientFactory.getEventBus(); PlaceController placeController = clientFactory.getPlaceController(); // activate activity manager and init display ActivityMapper activityMapper = new DemoActivityMapper(clientFactory); ActivityManager activityManager = new ActivityManager(activityMapper, eventBus); activityManager.setDisplay(containerWidget); // display default view with activated history processing DemoPlaceHistoryMapper historyMapper = GWT.create(DemoPlaceHistoryMapper.class); PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper); historyHandler.register(placeController, eventBus, defaultPlace); RootLayoutPanel.get().add(mainLayout); History.newItem("mail:"); } }
      
      



; package com.gshocklab.mvp.client; import com.google.gwt.activity.shared.ActivityManager; import com.google.gwt.activity.shared.ActivityMapper; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.shared.EventBus; import com.google.gwt.place.shared.PlaceController; import com.google.gwt.place.shared.PlaceHistoryHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.gshocklab.mvp.client.layout.AppLayout; import com.gshocklab.mvp.client.mvp.DemoActivityMapper; import com.gshocklab.mvp.client.mvp.DemoPlaceHistoryMapper; import com.gshocklab.mvp.client.mvp.place.MailPlace; public class MvpInActionEntryPoint implements EntryPoint { private SimplePanel containerWidget; private MailPlace defaultPlace = new MailPlace(); @Override public void onModuleLoad() { final AppLayout mainLayout = new AppLayout(); containerWidget = mainLayout.getAppContentHolder(); final ClientFactory clientFactory = GWT.create(ClientFactory.class); EventBus eventBus = clientFactory.getEventBus(); PlaceController placeController = clientFactory.getPlaceController(); // activate activity manager and init display ActivityMapper activityMapper = new DemoActivityMapper(clientFactory); ActivityManager activityManager = new ActivityManager(activityMapper, eventBus); activityManager.setDisplay(containerWidget); // display default view with activated history processing DemoPlaceHistoryMapper historyMapper = GWT.create(DemoPlaceHistoryMapper.class); PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper); historyHandler.register(placeController, eventBus, defaultPlace); RootLayoutPanel.get().add(mainLayout); History.newItem("mail:"); } }





私はすべてが単純明快で、コードの説明は不要だと思います。 History.newItem( "mail:")への呼び出しは不要な場合があることに注意してください。 MailPlaceがデフォルトの場所として指定されているため、MailActivityはすでに起動されています。 もう1つは、起動時に、ブラウザのアドレスバーにハッシュURL #mail:が表示されないことです。 プロジェクトの開始ハッシュURLを表示することが重要でない場合は、History.newItem()の呼び出しを削除できます。

組み込みのMVPフレームワークを機能させるには、GWTモジュールの記述ファイル(gwt.xmlファイル)内の対応するGWTモジュールを接続する必要があります

 <?xml version="1.0" encoding="UTF-8"?> <module rename-to='mvpinaction'> <inherits name='com.google.gwt.user.User' /> <inherits name="com.google.gwt.activity.Activity"/> <inherits name="com.google.gwt.place.Place"/> <entry-point class='com.gshocklab.mvp.client.MvpInActionEntryPoint' /> <replace-with class="com.gshocklab.mvp.client.ClientFactoryImpl"> <when-type-is class="com.gshocklab.mvp.client.ClientFactory" /> </replace-with> <source path='client' /> </module>
      
      



> <?xml version="1.0" encoding="UTF-8"?> <module rename-to='mvpinaction'> <inherits name='com.google.gwt.user.User' /> <inherits name="com.google.gwt.activity.Activity"/> <inherits name="com.google.gwt.place.Place"/> <entry-point class='com.gshocklab.mvp.client.MvpInActionEntryPoint' /> <replace-with class="com.gshocklab.mvp.client.ClientFactoryImpl"> <when-type-is class="com.gshocklab.mvp.client.ClientFactory" /> </replace-with> <source path='client' /> </module>



> <?xml version="1.0" encoding="UTF-8"?> <module rename-to='mvpinaction'> <inherits name='com.google.gwt.user.User' /> <inherits name="com.google.gwt.activity.Activity"/> <inherits name="com.google.gwt.place.Place"/> <entry-point class='com.gshocklab.mvp.client.MvpInActionEntryPoint' /> <replace-with class="com.gshocklab.mvp.client.ClientFactoryImpl"> <when-type-is class="com.gshocklab.mvp.client.ClientFactory" /> </replace-with> <source path='client' /> </module>



> <?xml version="1.0" encoding="UTF-8"?> <module rename-to='mvpinaction'> <inherits name='com.google.gwt.user.User' /> <inherits name="com.google.gwt.activity.Activity"/> <inherits name="com.google.gwt.place.Place"/> <entry-point class='com.gshocklab.mvp.client.MvpInActionEntryPoint' /> <replace-with class="com.gshocklab.mvp.client.ClientFactoryImpl"> <when-type-is class="com.gshocklab.mvp.client.ClientFactory" /> </replace-with> <source path='client' /> </module>





ClientFactoryインスタンスを作成するための遅延バインディングルールもここで指定されます。



結論の代わりに



それだけです。 AppLayoutアプリケーションのルートレイアウトをファイルに提供していません。ソースで確認できます。 このファイルには、href属性にアプリケーションサブシステムに移動するためのハッシュURLが含まれるリンクがあります。 ブラウザのアドレスバーに正しいURLを入力するだけで、1つまたは別のサブシステムを開くこともできます。 状態をURLから適切な場所に変換するプロセスは、対応するアクティビティの起動とともに自動的に開始され、必要なビューが表示されます。

さらに、このメモとデモプロジェクトでは、イベントバス(eventBus)の使用、ハッシュURLパラメーターの処理など、人生の重要な瞬間に対処していなかったことにも注意してください。



動作するデモプロジェクトGoogle Codeのソースコード 。 注意Mercurial!

フィードバックとコメントを待っています。



PS複数文字でごめんね。 この紹介ノートが誰かに役立つことを願っています(組み込みのMVPの完全な力を完全にはカバーしていませんが)、私はそれを理由で書いており、本当にクールなGWTアプリケーションの実装の出発点として役立つでしょう



All Articles