Ответ 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 могли бы предпринять соответствующие действия, как описано выше. Ведущий, контролирующий иерархию, предположительно также отправит сообщение на сервер, убедившись, что удаленный файл был фактически удален. Эта настройка позволяет модулям реагировать на события в других модулях, просто прослушивая события, запущенные из шины событий. Слишком мало сцепления происходит, и замена взглядов, презентаторов или обработчиков совершенно безболезненна.