ナカザンドットネット

それって私の感想ですよね

An MVP Pattern for Androidを訳してみた

新年あけましておめでとうございます。 ブログで書き初めというのも粋かなと思いまして、仕事でAndroidでのMVPパターンを調べている一環でAn MVP Pattern for Androidを意訳してみました。それではどうぞ。

An MVP Pattern for Android

Separating the presentation of the application's UI from the logic of its UI is usually a good idea. This separation of concerns creates more de-coupled code, which is much cleaner, and even allows for more unit testing coverage. Android bundles the UI and UI logic into the Activity class, which necessitates Instrumentation to test the Activity. Since Instrumentation is introduced, it is much more difficult (or impossible) to properly unit test your UI logic when the dependencies in the code cannot be mocked. However, a simple MVP pattern will help de-couple the UI and UI logic in Android applications.

一般的に、アプリのUIの見た目をロジックから切り離すのは良い考え方です。この関心の分離は、コードを疎結合で簡潔なものにし、ユニットテストカバレッジを改善することもできるでしょう。AndroidのUIとUIロジックはActivityに纏められており、ActivityをテストするにはInstrumentationの利用が必須となっていますが、Instrumentationの仕組みが現れて以来、コードの依存性をモック化できないUIロジックに対して適切なユニットテストを行うことが、非常に困難(または不可能)になっています。しかし、シンプルな MVPパターン を用いることで、Androidアプリケーション内のUIとUIロジックの分離をしやすくなります。

The MVP pattern stands for Model-View-Presenter, and it separates the UI concerns between the data of the UI (Model), the display of the UI (View), and the logic of the UI (Presenter). For Android, the View is the Activity, which will handle gathering user input and updating the display; the Presenter is a class that will handle communication between the Model and View; the Model will handle persisting and retrieving data, along with any business logic that the data must adhere to. Interfaces will be used to de-couple each of these components. A simple Customer View will be used to illustrate how this can be accomplished.

MVPパターンは、 Model-View-Presenter を表しており、UIに出すデータ( Model )、UIの見た目( View )、UIのロジック( Presenter )という、UIに関わる関心ごとを分離しています。Androidにおいては、ユーザーからの入力や画面更新を取りまとめているActivityView 、ModelとViewの間で行われるやりとりを制御するクラスが Presenter 、データの保持や検索など、データに従った形の各種ビジネスロジックを扱うクラスを Model と呼べばよいでしょう。各コンポーネント疎結合に保つための仕組みとしては、インターフェースを使います。シンプルなCustomer Viewを例に挙げて、これらをどの様に実現していくかを説明していきます。

An MVP Pattern for Android - Magenic Blog

First, our CustomerActivity (the View) will have textboxes for the Customer's ID, first name, and last name:

先ず、今回作るCustomerActivity(これが View ですね)は、カスタマーID、名前、名字のテキストボックスを持っています。

private EditText mFirstNameEditText, mLastNameEditText, mIdEditText;

The user will load a customer using the mIdEditText and a Load Button. Likewise, the user will save a customer using a Save Button:

ユーザーはカスタマー情報をmIdEditTextmLoadButtonを使ってロードします。また、同じように、カスタマー情報をmSaveButtonでセーブします。

private Button mSaveButton, mLoadButton;

We must now create a CustomerPresenter with the following methods:

さて、そろそろ以下のメソッドを持つCustomerPresenterクラスを作成しましょう。

public CustomerPresenter(ICustomerView View)
public void saveCustomer (String firstName, String lastName) 
public void loadCustomer (int id)

We then can wire it all up in the CustomerActivity's onCreate method:

ここでCustomerActivity#onCreateに繋いでいきます。

mCustomerPresenter = new CustomerPresenter(this);
mSaveButton.setOnClickListener(this); 
mLoadButton.setOnClickListener(this);

The CustomerActivity class must now implement the interfaces OnClickListener (for handling the Button's OnClickListeners) and ICustomerView (for the CustomerPresenter constructor). The OnClickListener defines the method void onClick(View v), and our method will look like the following:

CustomerActivityクラスにはインターフェースとしてOnClickListener(Button用のリスナ)と、ICustomerView(CustomerPresenterコンストラクタ用)を実装して下さい。また、OnClickListeneronClick(View v)を実装する必要がありますが、下記のようにして下さい。

switch (v.getId()) {
case R.id.saveButton:
    mCustomerPresenter.saveCustomer(mFirstNameEditText.getText().toString(), 
    mLastNameEditText.getText().toString());
    break;
case R.id.loadButton: 
    mCustomerPresenter.loadCustomer(Integer.parseInt(mIdEditText.getText().toString()));
    break;
}

The previous two code sections show that when the Save Button is clicked, the saveCustomer method of our presenter will be called with the Customer's first name and last name information; and when the Load Button is clicked, the loadCustomer method of our presenter will be called.

前2つのコードは、mSaveButtonがクリックされた時に PresentersaveCustomerメソッドが名字と名前を引数として呼ばれるという話と、mLoadButtonがクリックされた時に PresenterloadCustomerメソッドが呼ばれるという話でした。

We haven't defined ICustomerView, so we'll do that now. When loading the customer, the CustomerPresenter will need to be able to update the CustomerActivity's last name, first name, and ID EditTexts, so ICustomerView will look like the following:

まだICustomerViewに手を付けていませんでしたので、そろそろ定義しましょう。カスタマー情報をロードするときには、CustomerActivityの名字、名前、カスタマーIDのEditTextをCustomerPresenterから書き換えられるよう、ICustomerViewを下記のようにしましょう。

void setLastName (String lastName); 
void setFirstName (String firstName);
void setId(int id);

The CustomerActivity's implementation of these methods will set the corresponding EditText to the value of the parameter.

CustomerActivityへの(ICustomerViewの)実装では、各メソッドに対応するEditTextへパラメータをセットしておいてください。

The CustomerPresenter, then, will look like the following:

CustomerPresenterは下記のようになります。

private ICustomerView mCustomerView;
private ICustomerModel mCustomerModel;
public CustomerPresenter(ICustomerView view) {
    mCustomerView = view;
    mCustomerMode = new CustomerModel();
}
@Override
public void saveCustomer(String firstName, String lastName) {
    mCustomerModel.setFirstName(firstName);
    mCustomerModel.setLastName(lastName);
}
@Override
public void loadCustomer(int id) {
    (mCustomerModel.load(id)) 
    {
        mCustomerView.setId(mCustomerModel.getId());
        mCustomerView.setFirstName(mCustomerModel.getFirstName());
        mCustomerView.setLastName(mCustomerModel.getLastName());
    }
}

The implementation of the CustomerModel isn't important for our purposes; we just need to know that it is being saved to a repository of some sort. Furthermore, the CustomerModel is currently tightly-coupled to the CustomerPresenter, which can be remedied by injecting it as a dependency.

CustomerModelの実装は今回の目的から見るとあまり重要ではありません。何らかの保存場所にデータがセーブされていればよいのです。その上、今回のCustomerModelCustomerPresenterと密結合していますので、依存性注入を行う形などで修正したほうがよいでしょう。

The CustomerPresenter allows the CustomerActivity class to be as simple as possible. The activity now only gathers user input for the presenter and provides simple UI update methods for the presenter. Since the CustomerView and CustomerModel implement interfaces and can be injected into the CustomerPresenter, it is not dependent on them. Therefore, they can by mocked, which allows the CustomerPresenter logic to be unit tested.

CustomerPresenterによってCustomerActivityクラスは可能な限り完結になり得ます。今回のActivityPresenter にユーザーからの入力を集め、 Presenter から触るためのシンプルなUI更新メソッドを提供しています。CustomerViewCustomerModelはインターフェースにより実装されているため、CustomerPresenterからのインジェクションができるくらいに依存性がありません。そのため、それぞれのモックさえ作ればCustomerPresenterのロジックはユニットテストが可能になるのです。

After Note: This MVP pattern is sometimes referred to as Passive View, since the view only passes along data, either to or from its presenter. The MVP pattern can also be implemented such that the View knows of the model. The view responds to state changes in the model for simple UI updates, while the presenter handles more complex UI logic. This more complex pattern is sometimes referred to as Supervising Controller.

後記:今回のMVPパターンは、 View が単に Presenter に対して入出力するだけのものなので、Passive Viewパターンとも呼ばれるものになっています。MVPパターン自体は ViewModel にアクセスできる場合にも成り立ちます。 ViewModel の状態の変化に反応して、シンプルなUI更新を行い、複雑なUIロジックが必要な場合に Presenter に投げます。この複雑なパターンのことを特にSupervising Controllerパターンと呼ばれたりします。

In Android, this can be accomplished by the Model using Java's Observable class and the View implementing the Observer interface; when something changes in the Model, it can call the Observable's notifyObservers method. It can also be implemented with Android's Handler class; when something changes in the Model, it can send a message to a handler that the View injects into it.

Androidでは、JavaObservableクラスを用いた Model と、Observerインターフェースを実装した View によってMVPパターンが実現できます。 Model に変更があった時にObservable#notifyObserversを呼び出すのです。また、AndroidHandlerクラスでも同様の実装ができます。 Model に変更があった時に、 View に変更を伝えられるHandlerに対して、メッセージを投げるのです。

An MVP Pattern for Android - Magenic Blog]

訳注

言うとおりに作ったらこうなりました