Таким образом, вы используете платформу NetBeans и знаете, что она позволяет создавать действительно модульную конструкцию, в которую можно подключать и отключать компоненты по вашему желанию. Но теперь, какой шаблон вы можете использовать, чтобы уменьшить связь между компонентами, которые должны взаимодействовать друг с другом? Рассмотрим, например, проблему, с которой я сталкиваюсь с
blueMarine : некоторые компоненты («исследователи») позволяют запрашивать внутреннюю базу данных изображений с другими критериями (например, просмотр файловой системы, по метаданным, по тегу …), а другие компоненты (‘ Представления ‘) показать результат по-разному (например, миниатюры или панели свойств или полный рендеринг) Какой подход лучше: чтобы исследователи зависели от зрителей, чтобы они могли напрямую вызывать метод для них? Или наоборот?
Лучшее решение состоит в том, чтобы ни один из двух не зависел друг от друга. Вы можете добиться этого с помощью моего любимого шаблона развязки: «публиковать и подписываться» : определить набор сообщений («тем»), определить части кода, которые регистрируют интерес к этим темам и получить асинхронное уведомление («подписчики»), и определить части кода, которые публикуют и отменяют публикацию этих тем («издатели»). Тогда все готово. Все компоненты теперь могут общаться, не зная друг о друге, но только в зависимости от инфраструктуры обмена сообщениями и тем.
Действительно, это одна из первых частей, которую я разработал в blueMarine, когда я перенес ее на платформу NetBeans более двух лет назад, и это было довольно хорошее улучшение по сравнению с предыдущим проектом. Реализация была сфокусирована на классе Lookup платформы NetBeans, одном из самых мощных в платформе NetBeans. Хотя он в основном используется для поиска служб, зарегистрированных в приложении, он также используется средой выполнения платформы NetBeans для хранения соответствующих объектов (например, выбранных элементов в списке) и для уведомления слушателей (такие контекстно-зависимые действия). Поэтому я в основном использовал глобальный поиск, предоставляемый платформой NetBeans «Utilities.actionsGlobalContext ()», чтобы прослушивать выборки, сделанные в TopComponents. Это казалось довольно логичным способом работы,поскольку TopComponents обычно содержат ExplorerManager, который автоматически получает уведомления о выбранных узлах в деревьях или списках, размещенных внутри TopComponent. Итак, поток событий:
выберите узел в дереве -> уведомление опубликовано в ExplorerManager -> уведомление опубликовано для actionsGlobalContext () -> уведомление получено моим слушателем.
При таком подходе вы можете использовать информацию о классе в качестве «темы»: когда вы выбираете Node, все, что находится внутри его частного Lookup (например, DataObject), публикуется. DataObject, очевидно, является одной из наиболее часто используемых тем blueMarine (представляет фотографии) и предоставляет информацию о выборе одного объекта: для уведомления о том, что набор DataObjects был подготовлен, я определил определенный класс с именем DataObjectSelection; чтобы уведомить, что в фоновом режиме выполняется задача выбора, используется другой конкретный объект (DataObjectSelectionInProgress), чтобы зрители могли отобразить сообщение «Пожалуйста, подождите».
Идея была аккуратной, код реализации немного громоздким, и я столкнулся с некоторыми досадными ошибками. Основная проблема возникла из-за использования actionGlobalContext () и в зависимости от поведения TopComponent. Одна из вещей, которая иногда случалась, была эта последовательность: вы получаете файловый менеджер и рекурсивно выбираете каталог; в этот момент задание фонового сканирования начинается и длится некоторое время; тем временем вы активируете другой TopComponent (например, просмотрщик миниатюр, где вы ожидаете увидеть результаты в скором времени); на этом этапе сканер завершает работу и выбирает новые узлы в первом TopComponent, который, к сожалению, больше не является активным TopComponent. Итак, результаты не появляются. Чтобы их увидеть, нужно было снова активировать первый TopComponent. Слишком громоздко!
Несколько месяцев назад я узнал от Уэйда Чандлера , другого члена команды мечты NetBeans , что все можно сделать лучше. Он опубликовал очень компактный и аккуратный класс с именем CentralLookup : это одноэлементный объект, содержащий новый Lookup, который вы можете использовать по своему усмотрению , поскольку платформа NetBeans об этом не знает. Таким образом, вы можете сами выбирать свою политику публикации, не вмешиваясь в работу TopComponents. Иногда проблемы имеют аккуратные и простые решения.
Уэйд опубликовал свой код в инкубаторе PlatformX , и сегодня я использовал его в качестве основы для нового средства в blueMarine, названного EventBus. Основные функции предоставляются Wade’s CentralLookup, а EventBus оборачивает его более сфокусированным интерфейсом. По сути, мои исследователи теперь используют этот код для публикации:
DataObject myDataObject = ...
EventBus.getDefault().publish(myDataObject):
Зрители используют этот код для получения уведомлений:
private final EventBusListener<DataObject> listener = new EventBusListener<DataObject>()
{
@Override
public void notify (final DataObject dataObject)
{
if (dataObject != null)
{
// do something
}
}
};
где слушатель зарегистрирован так:
EventBus.getDefault().subscribe(DataObject.class, listener);
Самая актуальная версия опубликована в OpenBlueSky, вы можете проверить ее с помощью Subversion по адресу https://openbluesky.dev.java.net/svn/openbluesky/trunk/src/OpenBlueSky/EventBus ), я также разместив код ниже:
import it.tidalwave.openide.eventbus.impl.ListenerAdapter;
import java.util.HashMap;
import java.util.Map;
import org.netbeans.platformx.centrallookup.api.CentralLookup;
import org.openide.util.Lookup.Result;
public class EventBus
{
private static final EventBus instance = new EventBus();
private final CentralLookup centralLookup = CentralLookup.getDefault();
private final Map<Class<?>, Result<?>> resultMapByClass = new HashMap<Class<?>, Result<?>>();
private final Map<EventBusListener<?>, ListenerAdapter<?>> adapterMapByListener = new HashMap<EventBusListener<?>, ListenerAdapter<?>>();
private EventBus()
{
}
public static EventBus getDefault()
{
return instance;
}
public void publish (final Object object)
{
if (object == null)
{
throw new IllegalArgumentException("object is mandatory");
}
for (final Object old : centralLookup.lookupAll(object.getClass()))
{
centralLookup.remove(old);
}
if (object != null)
{
centralLookup.add(object);
}
}
public void unpublish (final Class<?> topic)
{
for (final Object old : centralLookup.lookupAll(topic))
{
centralLookup.remove(old);
}
}
public synchronized <T> void subscribe (final Class<T> topic, final EventBusListener<T> listener)
{
Result<?> result = resultMapByClass.get(topic);
if (result == null)
{
result = centralLookup.lookupResult(topic);
resultMapByClass.put(topic, result);
result.allInstances();
}
final ListenerAdapter<T> adapter = new ListenerAdapter<T>(listener);
adapterMapByListener.put(listener, adapter);
result.addLookupListener(adapter);
}
public synchronized <T> void unsubscribe (final Class<T> topic, final EventBusListener<T> listener)
{
final Result<?> result = resultMapByClass.get(topic);
if (result == null)
{
throw new IllegalArgumentException(String.format("Never subscribed to %s", topic));
}
final ListenerAdapter<T> adapter = (ListenerAdapter<T>)adapterMapByListener.remove(listener);
result.removeLookupListener(adapter);
}
}
Это EventBusListener:
public interface EventBusListener<T>
{
public void notify (T object);
}
и, наконец, это класс реализации утилиты:
import it.tidalwave.openide.eventbus.EventBusListener;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
public class ListenerAdapter<T> implements LookupListener
{
private final EventBusListener eventBusListener;
public ListenerAdapter (final EventBusListener eventBusListener)
{
this.eventBusListener = eventBusListener;
}
public void resultChanged (final LookupEvent event)
{
final Lookup.Result result = (Lookup.Result)event.getSource();
if (!result.allInstances().isEmpty())
{
eventBusListener.notify((T)result.allInstances().iterator().next());
}
else
{
eventBusListener.notify(null);
}
}
}