Cấu trúc chương trình
Đây là cách tôi đã làm nó. Eventbus
cho phép diễn giả (mở rộng lớp trừu tượng Subscriber
) đăng ký các sự kiện thuộc các mô hình khác nhau trong ứng dụng của tôi. Mỗi mô-đun tương ứng với một thành phần trong hệ thống của tôi và mỗi mô-đun có loại sự kiện, người trình bày, trình xử lý, chế độ xem và mô hình.
Người trình bày đăng ký tất cả các sự kiện thuộc loại CONSOLE
sẽ nhận tất cả các sự kiện được kích hoạt từ mô-đun đó. Đối với một cách tiếp cận tốt hơn, bạn luôn có thể cho phép người thuyết trình đăng ký các sự kiện cụ thể, chẳng hạn như NewLineAddedEvent
hoặc một cái gì đó tương tự, nhưng đối với tôi, tôi thấy rằng việc giải quyết nó ở cấp mô-đun là đủ tốt.
Nếu bạn muốn bạn có thể thực hiện cuộc gọi đến các phương thức cứu hộ của người trình bày không đồng bộ, nhưng cho đến nay tôi thấy mình không cần phải làm như vậy. Tôi cho rằng nó phụ thuộc vào nhu cầu chính xác của bạn là gì. Đây là tôi EventBus
:
public class EventBus implements EventHandler
{
private final static EventBus INSTANCE = new EventBus();
private HashMap<Module, ArrayList<Subscriber>> subscribers;
private EventBus()
{
subscribers = new HashMap<Module, ArrayList<Subscriber>>();
}
public static EventBus get() { return INSTANCE; }
public void fire(ScEvent event)
{
if (subscribers.containsKey(event.getKey()))
for (Subscriber s : subscribers.get(event.getKey()))
s.rescue(event);
}
public void subscribe(Subscriber subscriber, Module[] keys)
{
for (Module m : keys)
subscribe(subscriber, m);
}
public void subscribe(Subscriber subscriber, Module key)
{
if (subscribers.containsKey(key))
subscribers.get(key).add(subscriber);
else
{
ArrayList<Subscriber> subs = new ArrayList<Subscriber>();
subs.add(subscriber);
subscribers.put(key, subs);
}
}
public void unsubscribe(Subscriber subscriber, Module key)
{
if (subscribers.containsKey(key))
subscribers.get(key).remove(subscriber);
}
}
Trình xử lý được gắn liền với các thành phần, và chịu trách nhiệm chuyển nguồn gốc sự kiện GWT vào các sự kiện đặc biệt dành cho hệ thống của tôi. Trình xử lý dưới đây giao dịch với ClickEvents
chỉ đơn giản bằng cách gói chúng trong một sự kiện tùy chỉnh và kích hoạt chúng trên EventBus
để người đăng ký có thể xử lý. Trong một số trường hợp, điều này có ý nghĩa đối với những người xử lý để thực hiện kiểm tra bổ sung trước khi kích hoạt sự kiện hoặc đôi khi thậm chí trước khi quyết định thời tiết hoặc không gửi sự kiện. Hành động trong trình xử lý được đưa ra khi trình xử lý được thêm vào thành phần đồ họa.
public class AppHandler extends ScHandler
{
public AppHandler(Action action) { super(action); }
@Override
public void onClick(ClickEvent event)
{
EventBus.get().fire(new AppEvent(action));
}
Action
là cách liệt kê các cách xử lý dữ liệu có thể có trong hệ thống của tôi. Mỗi sự kiện được khởi tạo với một số Action
. Hành động được sử dụng bởi các diễn giả để xác định cách cập nhật chế độ xem của họ. Sự kiện với hành động ADD
có thể khiến người trình bày thêm nút mới vào menu hoặc hàng mới vào lưới.
public enum Action
{
ADD,
REMOVE,
OPEN,
CLOSE,
SAVE,
DISPLAY,
UPDATE
}
Sự kiện được trình xử lý kích hoạt trông hơi giống thế này. Lưu ý cách sự kiện định nghĩa một giao diện cho người tiêu dùng của nó, điều này sẽ đảm bảo rằng bạn không quên thực hiện các phương thức cứu hộ chính xác.
public class AppEvent extends ScEvent {
public interface AppEventConsumer
{
void rescue(AppEvent e);
}
private static final Module KEY = Module.APP;
private Action action;
public AppEvent(Action action) { this.action = action; }
Người trình bày đăng ký sự kiện thuộc mô-đun khác nhau, sau đó giải cứu họ khi họ bị sa thải. Tôi cũng cho phép mỗi người trình bày định nghĩa một giao diện cho chế độ xem của nó, điều đó có nghĩa là người trình bày sẽ không bao giờ phải biết gì về các thành phần graphcal thực tế.
public class AppPresenter extends Subscriber implements AppEventConsumer,
ConsoleEventConsumer
{
public interface Display
{
public void openDrawer(String text);
public void closeDrawer();
}
private Display display;
public AppPresenter(Display display)
{
this.display = display;
EventBus.get().subscribe(this, new Module[]{Module.APP, Module.CONSOLE});
}
@Override
public void rescue(ScEvent e)
{
if (e instanceof AppEvent)
rescue((AppEvent) e);
else if (e instanceof ConsoleEvent)
rescue((ConsoleEvent) e);
}
}
Mỗi chế độ xem được cung cấp một kiểu xử lý đúng cho mỗi chế độ xem. Mỗi nhà máy được khởi tạo với một Module
, mà nó sử dụng để tạo các trình xử lý của loại chính xác.
public ScHandler create(Action action)
{
switch (module)
{
case CONSOLE :
return new ConsoleHandler(action);
Chế độ xem giờ đây miễn phí để thêm bộ xử lý loại khác với thành phần mà không cần phải biết chi tiết triển khai chính xác.Trong ví dụ này, tất cả các khung nhìn cần biết là nút addButton
phải được liên kết với một số hành vi tương ứng với hành động ADD
. Hành vi này sẽ được quyết định bởi các diễn giả bắt sự kiện này.
public class AppView implements Display
public AppView(HandlerFactory factory)
{
ToolStripButton addButton = new ToolStripButton();
addButton.addClickHandler(factory.create(Action.ADD));
/* More interfacy stuff */
}
public void openDrawer(String text) { /*Some implementation*/ }
public void closeDrawer() { /*Some implementation*/ }
Ví dụ
Xem xét một Eclipse đơn giản, nơi bạn có một hệ thống phân cấp lớp bên trái, một vùng văn bản cho mã trên bên phải, và một thanh menu phía trên. Ba điều này sẽ có ba quan điểm khác nhau với ba diễn giả khác nhau và do đó họ sẽ tạo nên ba mô-đun khác nhau. Bây giờ, hoàn toàn có thể là vùng văn bản sẽ cần phải thay đổi theo các thay đổi trong hệ thống phân cấp lớp, và do đó nó có ý nghĩa đối với người trình bày vùng văn bản để đăng ký không chỉ với các sự kiện được kích hoạt trong vùng văn bản mà còn cho các sự kiện bị đuổi khỏi hệ thống phân cấp lớp. Tôi có thể tưởng tượng một cái gì đó như thế này (đối với mỗi module sẽ có một tập các lớp - một handler, một loại sự kiện, một trong những người dẫn chương trình, một trong những mô hình và một view):
public enum Module
{
MENU,
TEXT_AREA,
CLASS_HIERARCHY
}
Bây giờ xem xét chúng tôi muốn điểm của chúng tôi để cập nhật đúng khi xóa tập tin lớp học khỏi chế độ xem phân cấp. Điều này sẽ dẫn đến những thay đổi sau vào gui:
- Các tập tin lớp nên được loại bỏ khỏi hệ thống phân cấp lớp
- Nếu tập tin lớp được mở ra, và do đó có thể nhìn thấy trong vùng văn bản, nó nên được đóng lại.
Hai diễn giả, người điều khiển chế độ xem dạng cây và chế độ kiểm soát chế độ xem văn bản, cả hai sẽ đăng ký các sự kiện được kích hoạt từ mô-đun CLASS_HIERARCHY
. Nếu hành động của sự kiện là REMOVE
, cả hai phiên bản trước đều có thể thực hiện hành động thích hợp, như được mô tả ở trên. Người trình bày kiểm soát hệ thống phân cấp có lẽ cũng sẽ gửi một tin nhắn đến máy chủ, đảm bảo rằng tệp đã xóa đã thực sự bị xóa. Thiết lập này cho phép các mô-đun phản ứng với các sự kiện trong các mô-đun khác chỉ đơn giản bằng cách lắng nghe các sự kiện được kích hoạt từ xe buýt sự kiện. Có rất ít sự liên kết xảy ra, và trao đổi các quan điểm, người trình bày hoặc xử lý là hoàn toàn không đau.
Nhóm GWT đã đăng một ứng dụng mẫu từ các phương pháp hay nhất về GWT nói chuyện tại Google IO 2009 sử dụng MVP và EventBus - http://code.google.com/webtoolkit/articles/mvp-architecture.html – Anurag