サーチ…


単純なMVPの例

MVPパターンの簡単な使用例を説明するために、ボタンとラベルだけを持つ簡単なUIを作成する次のコードを考えてみましょう。ボタンがクリックされると、ボタンがクリックされた回数でラベルが更新されます。

私たちには5つのクラスがあります:

  • モデル - 状態を維持するPOJO(MVPのM)
  • View - UIコードを持つクラス(MVPのV)
  • ViewListener - ビュー内のアクションに応答するメソッドを提供するインタフェース
  • プレゼンター - 入力に応答し、ビューを更新します(MVPのP)
  • アプリケーション - すべてをまとめてアプリを起動する「メイン」クラス

単一のcount変数を維持する最小の「モデル」クラス。

/**
 * A minimal class to maintain some state 
 */
public class Model {
    private int count = 0;

    public void addOneToCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

リスナーに通知する最小限のインタフェース:

/**
 * Provides methods to notify on user interaction
 */
public interface ViewListener {
    public void onButtonClicked();
}

ビュークラスはすべてのUI要素を作成します。ビューとビューのみが 、UI要素(プレゼンターや他のクラスのボタンやテキストフィールドなどはありません)を参照する必要があります。

/**
 * Provides the UI elements
 */

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

public class View {
    // A list of listeners subscribed to this view
    private final ArrayList<ViewListener> listeners;
    private final JLabel label;
    
    public View() {
        final JFrame frame = new JFrame();
        frame.setSize(200, 100);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(new GridLayout());

        final JButton button = new JButton("Hello, world!");

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                notifyListenersOnButtonClicked();
            }
        });
        frame.add(button);

        label = new JLabel();
        frame.add(label);

        this.listeners = new ArrayList<ViewListener>();

        frame.setVisible(true);
    }

    // Iterate through the list, notifying each listner individualy 
    private void notifyListenersOnButtonClicked() {
        for (final ViewListener listener : listeners) {
            listener.onButtonClicked();
        }
    }

    // Subscribe a listener
    public void addListener(final ViewListener listener) {
        listeners.add(listener);
    }

    public void setLabelText(final String text) {
        label.setText(text);
    }
}

通知ロジックは、Java8で次のようにコーディングすることもできます。

        ...
        final Button button = new Button("Hello, world!");
        // In order to do so, our interface must be changed to accept the event parametre
        button.addActionListener((event) -> {
            notifyListeners(ViewListener::onButtonClicked, event);
            // Example of calling methodThatTakesALong, would be the same as callying:
            // notifyListeners((listener, long)->listener.methodThatTakesALong(long), 10L)
            notifyListeners(ViewListener::methodThatTakesALong, 10L);
        });
        frame.add(button);
        ...

/**
 * Iterates through the subscribed listeneres notifying each listener individually.
 * Note: the {@literal '<T>' in private <T> void} is a Bounded Type Parametre. 
 *
 * @param <T>      Any Reference Type (basically a class).
 * 
 * @param consumer A method with two parameters and no return, 
 *                 the 1st parametre is a ViewListner, 
 *                 the 2nd parametre is value of type T.
 * 
 * @param data     The value used as parametre for the second argument of the
 *                 method described by the parametre consumer.
 */
private <T> void notifyListeners(final BiConsumer<ViewListener, T> consumer, final T data) {
    // Iterate through the list, notifying each listener, java8 style 
    listeners.forEach((listener) -> {

        // Calls the funcion described by the object consumer.
        consumer.accept(listener, data);

        // When this method is called using ViewListener::onButtonClicked
        // the line: consumer.accept(listener,data); can be read as:
        // void accept(ViewListener listener, ActionEvent data) {
        //     listener.onButtonClicked(data);
        // }
        
    });
}

ActionEventをパラメータとして受け取るには、インタフェースをリファクタリングする必要があります。

public interface ViewListener {
    public void onButtonClicked(ActionEvent evt);
    // Example of methodThatTakesALong signature
    public void methodThatTakesALong(long );
}

ここではnotify-methodは1つだけ必要ですが、実際のリスナメソッドとそのパラメータはパラメータとして渡されます。必要に応じて、これは実際のイベントハンドリングより少し面白くないものにも使用できます。インターフェイスにメソッドがある限り、すべて動作します。例:

        notifyListeners(ViewListener::methodThatTakesALong, -1L);

プレゼンターは、ビューを取り込んで、それ自体をリスナーとして追加できます。ビュー内でボタンをクリックすると、ビューはすべてのリスナ(プレゼンタを含む)に通知します。発表者に通知されたので、モデル(つまり、アプリケーションの状態)を更新し、それに応じてビューを更新する適切なアクションを取ることができます。

/**
 * Responsible to responding to user interaction and updating the view
 */
public class Presenter implements ViewListener {
    private final View view;
    private final Model model;

    public Presenter(final View view, final Model model) {
        this.view = view;
        view.addListener(this);
        this.model = model;
    }

    @Override
    public void onButtonClicked() {
        // Update the model (ie. the state of the application)
        model.addOneToCount();
        // Update the view
        view.setLabelText(String.valueOf(model.getCount()));
    }
}

すべてをまとめるために、ビューを作成してプレゼンターに注入することができます。同様に、初期モデルを作成して注入することができます。どちらプレゼンターで作成することできますが、コンストラクターにそれらを注入することで、はるかに簡単なテストが可能になります。

public class Application {
    public Application() {
        final View view = new View();
        final Model model = new Model();
        new Presenter(view, model);
    }

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Application();
            }
        });
    }
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow