GWT MVP

建立任何大规模的应用都有其障碍,GWT的应用程序也不例外。多个开发者基于同样的代码并行开发,往往会让代码变得混乱,难于维护。为了帮助理清头绪,我们介绍设计模式到我们的项目中。

有各种各样的设计模式供我们选择:Presentation-abstraction-control, Model-view-controller, Model-view-presenter等等。每种设计模式都有自己的优点,但是我们发现Model-view-presenter (MVP)这种模式最适合GWT。原因有二:

简单的例子

一旦理解了MVP的基本原理,创建一个基于MVP的应用将会变得非常容易。为了帮助理解,这里提供了一个简单的应用Contacts作为例子。

MVP_Diagram

在开始之前,要先介绍一下基本组件:

Model

模型包含了业务对象,在本例中,有

View

视图包含了所有的UI组件,负责UI组件的布局。它没有Model的概念,就是说它并不知道它将要展示的是Contact联系人列表。 本例中,视图有

Presenter

Presenter包含了本例中所有的逻辑,如历史管理、视图转换、数据同步等。一个普遍的规则是,对每一个视图,都需要一个Presenter来控制视图和处理事件Event。 本例中,包含

可以看到Presenter与View是一一对应的。

整个示例工程的结构如下:

App_Hierarchy

接下来,我们来看看这些组件是如何进行交互的。

绑定Presenter和View

先看看示例中ContactView的界面

Contact_View

该视图有三个组件,两个按钮和一个表格。对应的Presenter需要做

在ContactsPresenter中,定义一个Display的接口

public class ContactsPresenter implements Presenter {
//...
public interface Display extends HasValue<List<String>> {
HasClickHandlers getAddButton();
HasClickHandlers getDeleteButton();
HasClickHandlers getList();
void setData(List<String> data);
int getClickedRow(ClickEvent event);
List<Integer> getSelectedRows();
Widget asWidget();
}
}

方法setData()用来获取Model数据到View,而无需关心Model的内部结构。这样的优点在于当Model发生变化的时候,不用修改View的代码。我们来看看它是如何获取服务端的数据:

public class ContactsPresenter implements Presenter {
//...
private void fetchContactDetails() {
rpcService.getContactDetails(new AsyncCallback<ArrayList<ContactDetails>>() {
public void onSuccess(ArrayList<ContactDetails> result) {
contacts = result;
List<String> data = new ArrayList<String>();

for (int i = 0; i < result.size(); ++i) {
data.add(contacts.get(i).getDisplayName());
}

display.setData(data);
}

public void onFailure(Throwable caught) {
//...
}
});
}
}

监听UI的事件:

public class ContactsPresenter implements Presenter {
//...
public void bind() {
display.getAddButton().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
eventBus.fireEvent(new AddContactEvent());
}
});

display.getDeleteButton().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
deleteSelectedContacts();
}
});

display.getList().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
int selectedRow = display.getClickedRow(event);

if (selectedRow >= 0) {
String id = contacts.get(selectedRow).getId();
eventBus.fireEvent(new EditContactEvent(id));
}
}
});
}
}

响应UI上面的操作,例如删除选中的记录:

public class ContactsPresenter implements Presenter {
//...
private void deleteSelectedContacts() {
List<Integer> selectedRows = display.getSelectedRows();
ArrayList<String> ids = new ArrayList<String>();

for (int i = 0; i < selectedRows.size(); ++i) {
ids.add(contactDetails.get(selectedRows.get(i)).getId());
}

rpcService.deleteContacts(ids, new AsyncCallback<ArrayList<ContactDetails>>() {
public void onSuccess(ArrayList<ContactDetails> result) {
contactDetails = result;
List<String> data = new ArrayList<String>();

for (int i = 0; i < result.size(); ++i) {
data.add(contactDetails.get(i).getDisplayName());
}

display.setData(data);
}

public void onFailure(Throwable caught) {
//...
}
});
}
}

再次申明,为了充分了解MVP的优点,Presenter应该不认识任何基础组件代码。只要把View封装在Display的接口中,这样鱼和熊掌都可兼得,一方面减少了GWT各组件之间的联系,另一方面依然可以实例化一个Display对象到面板上。

Events and the Event Bus