簡單的 MVP 示例
為了說明 MVP 模式的簡單示例用法,請考慮以下程式碼,該程式碼僅使用按鈕和標籤建立簡單 UI。單擊該按鈕時,標籤會更新按鈕被單擊的次數。
我們有 5 個類:
- 模型 - 保持狀態的 POJO(MVP 中的 M)
- View - 具有 UI 程式碼的類(MVP 中的 V)
- ViewListener - 提供響應檢視中操作的方法的介面
- 演示者 - 響應輸入並更新檢視(MV 中的 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,實際的 listener 方法及其引數作為引數傳遞。在需要的情況下,這也可以用於比實際事件處理稍微不那麼漂亮的東西,只要介面中有方法,它都可以工作,例如:
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();
}
});
}
}