Подтвердить что ты не робот

Есть ли рекомендуемый способ использования шаблона Observer в MVP с использованием GWT?

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

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

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

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

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

4b9b3361

Ответ 1

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

Вот как я это сделал. 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, который отвечает за создание правильного типа обработчика для каждого представления. Каждый factory создается с помощью 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
}

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

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

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

Ответ 2

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

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

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

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

Edit1:

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

Публикация ссылки на мою статью в блоге: Приложение GXT-GWT

Edit2:

Наконец-то суп-код. Публикация 1 Публикация 2 Публикация 3