2010-05-14 6 views
16

Tôi đang suy nghĩ về việc triển khai giao diện người dùng theo mẫu MVP sử dụng GWT, nhưng có nghi ngờ về cách tiếp tục.Có cách nào được khuyến nghị sử dụng mẫu Observer trong MVP bằng GWT không?

Đó là (một số) mục tiêu của tôi: (. Tức là không sử dụng các từ com.google *)

  • người dẫn chương trình không biết gì về công nghệ giao diện người dùng
  • quan điểm không biết gì về người dẫn chương trình (không chắc nữa nếu tôi muốn nó trở thành mô hình-agnostic, chưa)
  • mô hình không biết gì về quan điểm hoặc người dẫn chương trình (... rõ ràng)

tôi sẽ đặt một giao diện giữa quan điểm và người giới thiệu và sử dụng mẫu Observer để tách hai mẫu: chế độ xem tạo ra các sự kiện và người trình bày được thông báo.

Điều khiến tôi nhầm lẫn là java.util.Observer và java.util.Observable không được hỗ trợ trong GWT. Điều này cho thấy rằng những gì tôi đang làm không phải là cách được khuyến nghị để làm điều đó, theo như GWT có liên quan, dẫn tôi đến câu hỏi của tôi: cách nào được khuyến nghị để thực hiện MVP bằng GWT, đặc biệt với các mục tiêu trên? Bạn sẽ làm điều này như thế nào?

+3

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

Trả lời

31

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:

  1. Các tập tin lớp nên được loại bỏ khỏi hệ thống phân cấp lớp
  2. 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.

+0

Đây là một cách tiếp cận khá phức tạp và một câu trả lời mở rộng, cảm ơn bạn. Bạn có thể mở rộng thêm một chút bằng cách giải thích vai trò của Người đăng ký là gì? Ngoài ra, ý tưởng đằng sau việc đăng ký người trình bày với các mô-đun là gì? Cách tôi đang nghĩ về nó, một người trình bày có liên quan mạnh mẽ và được bản địa hóa thành một cặp mô hình xem cụ thể. Ngoài ra, bạn sử dụng một lớp Action để xác định tất cả các hành động có thể được kích hoạt từ bất kỳ phần nào của giao diện người dùng: nó có trở nên lộn xộn khi đủ các lớp thêm hành động của họ không? Ngoài ra, điều đó có nghĩa là những thay đổi trong bất kỳ mô-đun nào sẽ ảnh hưởng đến Hành động, điều này nghe có vẻ hơi đáng lo ngại ... –

+0

Tôi đã chỉnh sửa câu trả lời của mình để trả lời các câu hỏi tiếp theo của bạn. –

+0

Banang, cảm ơn vì nỗ lực và thông tin chi tiết. Tôi sẽ thử và xem nó như thế nào ... –

2

Tôi đã đạt được điều gì đó trên những dòng này cho dự án của chúng tôi. Tôi muốn có một cơ chế hướng sự kiện (nghĩ về PropertyChangeSupport và PropertyChangeListener của lib jdk chuẩn) bị thiếu. Tôi tin rằng có một mô-đun mở rộng và quyết định đi trước với chính tôi. Bạn có thể google nó cho propertychangesupport gwt và sử dụng nó hoặc đi với cách tiếp cận của tôi.

Phương pháp tiếp cận logic của tôi tập trung xung quanh MessageHandler và GWTEvent. Chúng phục vụ cho mục đích tương tự như của PropertyChangeListener và PropertyChangeEvent tương ứng. Tôi đã phải tùy chỉnh chúng vì những lý do được giải thích sau. Thiết kế của tôi liên quan đến một MessageExchange, MessageSender và MessageListener. Việc trao đổi hoạt động như một dịch vụ phát sóng gửi tất cả các sự kiện cho tất cả người nghe. Mỗi người gửi kích hoạt các sự kiện được lắng nghe bởi Exchange và trao đổi sẽ kích hoạt các sự kiện một lần nữa. Mỗi người nghe lắng nghe sự trao đổi và có thể tự quyết định (để xử lý hay không xử lý) dựa trên sự kiện.

Thật không may MessageHandlers trong GWT bị một vấn đề: "Trong khi một sự kiện đang được tiêu thụ, không có trình xử lý mới có thể được nối". Lý do được đưa ra trong biểu mẫu GWT: Trình vòng lặp sao lưu giữ các trình xử lý không thể được đồng thời sửa đổi bởi một luồng khác. Tôi đã phải viết lại việc triển khai tùy chỉnh các lớp GWT. Đó là ý tưởng cơ bản.

Tôi đã đăng mã, nhưng tôi đang trên đường đến sân bay ngay bây giờ, sẽ cố gắng đăng mã ngay sau khi tôi có thể dành thời gian.

Edit1:

Chưa thể lấy mã thực tế, có tổ chức của một số slide power-điểm tôi đã làm việc trên cho tài liệu thiết kế và tạo ra một blog entry.

Đăng một liên kết đến bài viết blog của tôi: GXT-GWT App

Edit2:

Cuối cùng một ít súp mã. Posting 1 Posting 2 Posting 3

+0

Cảm ơn bạn đã câu trả lời. Bạn có thể so sánh MessageExchange của mình với EventBus của Banang không? Tôi không chắc chắn tại thời điểm bạn nhận được bằng cách làm cho tất cả người nghe đánh giá mọi ứng dụng sự kiện để xem đó có phải là điều họ nên xử lý hay không, đó là cách tôi hiểu cách tiếp cận MessageExchange hoạt động ... –

+0

Câu trả lời nhanh; một số gọi nó là potaatoe, một số gọi nó là potaytoe! Tôi thực sự không cố gắng đạt được MVP ngay từ đầu. Tôi đã cố gắng để đạt được một giải pháp hướng sự kiện. Thành viên nhóm của tôi sau đó đã hướng dẫn tôi về bài viết của Martin Fowler về MVP. Tôi rất vui vì thiết kế đã lành mạnh và thực sự phù hợp với một mô hình. Sự khác biệt chính từ MVP: Trong chế độ xem MVP, hãy lắng nghe người thuyết trình. Trong trường hợp của tôi, bất kỳ thực thể nào cũng có thể nghe bất kỳ (chuỗi thính giả/mô hình).Thông tin liên lạc qua các sự kiện Ngoại lệ: Giao tiếp phía máy chủ, tôi đã viết một lớp điều khiển công văn (cho mục đích thử nghiệm đơn vị) contd: – questzen

+0

Lý do: Tôi trực quan hóa thiết kế của tôi làm trao đổi qua điện thoại. Trong thế giới thực, chúng ta không thể có một đường dây điện thoại giữa mọi thiết bị cầm tay: các đường NC2 hoặc N (N-1)/2. Chúng tôi kết nối với một cuộc trao đổi để chỉ cần N dòng. Hãy suy nghĩ của mỗi dòng như là một tham chiếu đến người nghe, việc truyền đối tượng tham chiếu sẽ được giảm đáng kể. IMO, MVP thực sự không yêu cầu trao đổi tập trung/xe buýt. Người ta có thể dễ dàng nhìn thấy lợi ích của xe buýt sự kiện (trong khi mã hóa) và sử dụng nó. Giải pháp của Banang là một giải pháp hay. Giải pháp của tôi dựa trên thành phần khi cô ấy sử dụng thừa kế. Một sự khác biệt về ý kiến, tôi đoán vậy. – questzen