Есть ли рекомендуемый способ использования шаблона Observer в MVP с использованием GWT? - PullRequest
17 голосов
/ 14 мая 2010

Я думаю о реализации пользовательского интерфейса в соответствии с шаблоном MVP с использованием GWT, но сомневаюсь, как действовать дальше.

Это (некоторые из) моих целей:

  • докладчик ничего не знает о технологии пользовательского интерфейса (т.е. ничего не использует от com.google. *)
  • представление ничего не знает о докладчике (пока не уверен, хотел бы я, чтобы он был независимым от модели)
  • модель ничего не знает о представлении или предъявителе (... очевидно)

Я бы поместил интерфейс между представлением и презентатором и использовал бы шаблон Observer для их разделения: представление генерирует события, и докладчик получает уведомление.

Что меня смущает, так это то, что java.util.Observer и java.util.Observable не поддерживаются в GWT. Это говорит о том, что то, что я делаю, не является рекомендуемым способом сделать это, что касается GWT, что приводит меня к моим вопросам: каков рекомендуемый способ реализации MVP с использованием GWT, особенно с учетом вышеуказанных целей? Как бы вы это сделали?

Ответы [ 3 ]

32 голосов
/ 14 мая 2010

Структура программы

Вот как я это сделал. Eventbus позволяет докладчикам (расширяющим абстрактный класс Subscriber) подписываться на события, принадлежащие различным модулям в моем приложении. Каждый модуль соответствует компоненту в моей системе, и у каждого модуля есть тип события, презентатор, обработчик, представление и модель.

Докладчик, подписавшийся на все события типа CONSOLE, получит все события, инициированные этим модулем. Для более детального подхода вы всегда можете позволить докладчикам подписываться на определенные события, такие как NewLineAddedEvent или что-то в этом роде, но для меня я обнаружил, что работа с ним на уровне модуля была достаточно хорошей.

Если вы хотите, вы можете сделать вызов методов спасения докладчиком асинхронным, но пока я не нашел особой необходимости делать это самостоятельно. Я полагаю, это зависит от ваших потребностей. Это мой 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);
    }

}

Обработчики прикреплены к компонентам и отвечают за преобразование собственных событий GWT в события, специализированные для моей системы. Приведенный ниже обработчик имеет дело с ClickEvents, просто заключая их в настраиваемое событие и вызывая их на EventBus, чтобы подписчики имели дело с ними. В некоторых случаях для обработчиков имеет смысл выполнить дополнительные проверки перед запуском события, а иногда даже перед принятием решения о погоде или не отправлять событие. Действие в обработчике дается, когда обработчик добавляется в графический компонент.

public class AppHandler extends ScHandler
{
    public AppHandler(Action action) { super(action); }

    @Override
    public void onClick(ClickEvent event) 
    { 
         EventBus.get().fire(new AppEvent(action)); 
    }

Action - это перечисление, выражающее возможные способы манипулирования данными в моей системе. Каждое событие инициализируется с Action. Действие используется докладчиками, чтобы определить, как обновить их представление. Событие с действием ADD может заставить докладчика добавить новую кнопку в меню или новую строку в сетке.

public enum Action 
{
    ADD,
    REMOVE,
    OPEN,
    CLOSE,
    SAVE,
    DISPLAY,
    UPDATE
}

Событие, которое запускается обработчиком, выглядит примерно так. Обратите внимание, как событие определяет интерфейс для своих потребителей, что гарантирует, что вы не забудете реализовать правильные методы восстановления.

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; }

Докладчик подписывается на события, относящиеся к разным модулям, а затем спасает их при запуске. Я также позволил каждому докладчику определять интерфейс для своего представления, что означает, что докладчику никогда не придется ничего знать о реальных графических компонентах.

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);
    }
}

Каждому представлению присваивается экземпляр HandlerFactory, который отвечает за создание правильного типа обработчика для каждого представления. Каждая фабрика создается с Module, который используется для создания обработчиков правильного типа.

public ScHandler create(Action action)
{
  switch (module)
  {
    case CONSOLE :
      return new ConsoleHandler(action);

Теперь представление может свободно добавлять обработчики различного вида к своим компонентам, не зная точных деталей реализации. В этом примере все, что нужно знать представлению, это то, что кнопка addButton должна быть связана с некоторым поведением, соответствующим действию ADD. Что это за поведение будет решать докладчик, который поймает событие.

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*/ }

Пример

Рассмотрим упрощенное Eclipse, в котором у вас есть иерархия классов слева, текстовая область для кода справа и строка меню сверху. Эти три будут тремя разными представлениями с тремя разными докладчиками, и поэтому они составят три разных модуля. Теперь вполне возможно, что текстовая область должна будет изменяться в соответствии с изменениями в иерархии классов, и поэтому имеет смысл для докладчика текстовой области подписываться не только на события, запускаемые из текстовой области, но и на события быть уволенным из иерархии классов. Я могу представить что-то вроде этого (для каждого модуля будет набор классов - один обработчик, один тип события, один презентатор, одна модель и одно представление):

public enum Module 
{
   MENU,
   TEXT_AREA,
   CLASS_HIERARCHY
}

Теперь рассмотрим, что мы хотим, чтобы наши представления обновлялись должным образом после удаления файла класса из представления иерархии. Это должно привести к следующим изменениям в графическом интерфейсе:

  1. Файл класса должен быть удален из иерархии классов
  2. Если файл класса открыт и, следовательно, виден в текстовой области, его следует закрыть.

Два докладчика, один, управляющий древовидным представлением, и другой, управляющий текстовым представлением, будут оба подписываться на события, запускаемые из модуля CLASS_HIERARCHY. Если действие события REMOVE, оба preseneters могут предпринять соответствующее действие, как описано выше. Презентатор, управляющий иерархией, предположительно также отправит сообщение на сервер, убедившись, что удаленный файл действительно был удален. Эта настройка позволяет модулям реагировать на события в других модулях, просто прослушивая события, запущенные из шины событий. Происходит очень мало связи, и обмен представлениями, докладчиками или обработчиками совершенно безболезненен.

2 голосов
/ 14 мая 2010

Я достиг чего-то на этих линиях для нашего проекта. Я хотел механизм, управляемый событиями (например, PropertyChangeSupport и PropertyChangeListener стандартной jdk lib), которые отсутствовали. Я считаю, что есть модуль расширения и решил продолжить с моим собственным. Вы можете отыскать его в Google для propertychangesupport gwt и использовать его или использовать мой подход.

Мой подход включал логику, сосредоточенную вокруг MessageHandler и GWTEvent. Они служат той же цели, что и PropertyChangeListener и PropertyChangeEvent соответственно. Я должен был настроить их по причинам, объясненным позже. Мой дизайн включал в себя MessageExchange, MessageSender и MessageListener. Биржа действует как широковещательная служба, отправляющая все события всем слушателям. Каждый отправитель запускает события, которые прослушиваются Exchange, и обмен запускает события снова. Каждый слушатель слушает обмен и может решить для себя (обрабатывать или не обрабатывать) на основе события.

К сожалению, MessageHandlers в GWT страдают от проблемы: «Пока используется событие, никакие новые обработчики не могут быть подключены». Причина, приведенная в форме GWT: вспомогательный итератор, содержащий обработчики, не может быть одновременно изменен другим потоком. Мне пришлось переписать пользовательскую реализацию классов GWT. Это основная идея.

Я бы отправил код, но сейчас я еду в аэропорт, постараюсь опубликовать код, как только смогу найти время.

Edit1:

Пока не смог получить реальный код, получил несколько слайдов, на которых я работал над проектной документацией, и создал запись в блоге.

Размещение ссылки на мою статью в блоге: GXT-GWT App

Edit2:

Наконец немного кодового супа. Проводка 1 Проводка 2 Проводка 3

0 голосов
/ 22 января 2015

взгляните на: http://www.gwtproject.org/javadoc/latest/com/google/gwt/event/shared/EventBus.html

(что устарело http://www.gwtproject.org/javadoc/latest/com/google/web/bindery/event/shared/EventBus.html)

Он должен нормально работать с GWT, как я сейчас попробую сам.

...