Структура программы
Вот как я это сделал. 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
}
Теперь рассмотрим, что мы хотим, чтобы наши представления обновлялись должным образом после удаления файла класса из представления иерархии. Это должно привести к следующим изменениям в графическом интерфейсе:
- Файл класса должен быть удален из иерархии классов
- Если файл класса открыт и, следовательно, виден в текстовой области, его следует закрыть.
Два докладчика, один, управляющий древовидным представлением, и другой, управляющий текстовым представлением, будут оба подписываться на события, запускаемые из модуля CLASS_HIERARCHY
. Если действие события REMOVE
, оба preseneters могут предпринять соответствующее действие, как описано выше. Презентатор, управляющий иерархией, предположительно также отправит сообщение на сервер, убедившись, что удаленный файл действительно был удален. Эта настройка позволяет модулям реагировать на события в других модулях, просто прослушивая события, запущенные из шины событий. Происходит очень мало связи, и обмен представлениями, докладчиками или обработчиками совершенно безболезненен.