Статьи

Выбор на основе селектора


Вы используете
платформу NetBeans или API поиска NetBeans? Нет ..? Не волнуйтесь, предлагаемый метод может быть адаптирован к различным контекстам, продолжайте читать. С помощью этого метода вы можете реально отделить действия в вашем приложении вместе с их включением. Независимо и с низкой связью, модули могут вносить новые активаторы действий (селекторы) и / или новые контекстно-разрешенные действия.

Предоставляется пример приложения и готовый к использованию модуль NetBeans .

 

Контекст и проблема

Короткая презентация поисков? (пропустите, если знаете)

Эта статья иллюстрируется с помощью NetBeans «Поиски». Если вы никогда не слышали о поисках или действительно не знаете их, этот короткий раздел поможет вам понять статью.

«Lookup» — это в основном универсальный контейнер, который можно прослушивать. При поиске можно зарегистрировать прослушиватель для объектов, реализующих любой интерфейс по их выбору. Затем слушатели уведомляются, когда некоторые объекты добавляются или удаляются из поиска. Если вы привыкли к сервис-ориентированным архитектурам, вы также можете увидеть его как общий сервисный репозиторий (который может содержать любой объект).

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

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

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

Где применяется метод?

Представленный метод может рассматриваться как архитектурный шаблон, который можно адаптировать во многих контекстах, где важны модульность и адаптивность. Он представлен здесь в контексте API поиска для платформы NetBeans. Представленная реализация легко может использоваться в приложениях на платформе NetBeans (или в приложениях, использующих API Lookup в простом старом приложении Java).

Что это решает?

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

  • любой модуль может добавить новое действие,
  • любой модуль может внести вклад в «стимулирующую стратегию» любого (существующего) типа действия,
  • любой модуль может добавить новое действие, которое следует любой существующей «разрешающей стратегии».

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

Пример использования вариантов (пропустите, если вы спешите)

Если вы спешите, попробуйте перейти непосредственно к предложенному решению в разделе «Подход».

Оригинальный вариант использования: действия в OMiSCID Gui

OMiSCID Gui — это графическое приложение для мониторинга и взаимодействия со службами, работающими в интеллектуальной среде. Среда содержит такие службы, как камеры, микрофоны, контроллеры освещения, службы преобразования текста в речь, громкоговорители и т. Д. Этот набор служб зависит от среды и со временем развивается и, как ожидается, будет расти в количестве и разнообразии.

Основная функциональность OMiSCID Gui заключается в перечислении запущенных сервисов и их свойств. Из этого списка пользователи могут получить контекстное меню. Действия, представленные в этом меню, зависят как от выбранных в данный момент сервисов, так и от модулей, установленных в приложении.

Вот ведущий пример с камерами. Мы хотим получить действие, которое потребует услуги камеры и откроет панель с отображением этой камеры в режиме реального времени. Мы также хотим другое действие, которое будет принимать камеру и кодировать ее в виде видеофайла. Мы можем представить больше действий, работающих с камерами. Каждое из вышеперечисленных действий должно сказать: «Я включен, когда в дереве выбрана служба с именем« Камера »». Указание каждого действия приводит к дублированию кода … но это может быть приемлемым.

Настоящая проблема возникает, когда вы видите, что кто-то создает «VideoFileReaderService», а другой — «ScreenCaptureService». Эти два новых вида услуг могут, как услуга «Камера», предоставлять поток изображений (хотя они не являются точно камерами). Вы хотите, чтобы все вышеперечисленные действия работали не только с услугами «Камера», но и с этими двумя новыми видами услуг. Очевидно, что вы хотите избежать изменения всех этих действий еще более избыточным кодом.

Наш метод позволяет избежать необходимости изменять все действия, когда новые условия выбора должны разрешать действия. Забота о принятии решения о том, какие действия должны быть разрешены (на основе выбора), относится к объектам, называемым «селекторами». «Селекторы» производят некоторые «Задачи», а действия на самом деле являются контекстно-зависимыми, используя эти «Задачи» в качестве контекста.

Пример иллюстративного проекта

Для предоставления некоторого упрощенного кода с использованием метода доступно простое приложение . Это приложение просто отображает список клиентов, каждый из которых имеет различные атрибуты, включая имя и место, где он или она живет. Клиенты представлены экземплярами класса Customer. Основываясь на выборе пользователя в этом списке, мы хотим включить или отключить некоторые действия. Код фактически основан на примере поста Гиртджана о модульности.

По сути, мы сначала хотим, чтобы первое действие было включено, когда выбран один и только один Клиент (ничего особенного для этого действия). Это классический случай контекстно-зависимого действия (ранее CookieAction), в котором поиск по умолчанию используется для автоматического включения / отключения в зависимости от выбора. Такое действие можно легко создать в NetBeans с помощью мастера действий, которое просто регистрирует действие в файле layer.xml и требует от вас реализации ActionListener.

Теперь мы хотим идти дальше и, как и в случае OMiSCID Gui, мы хотим четко отделить «когда» и «как». Точнее, мы отделяем «когда сообщение может быть создано на основе выбора клиентов» и «как оно должно быть обработано» (например, показывая окно сообщения, запись в файл журнала и т. Д.). Метод в дальнейшем делает это возможным.

Подход: выбор на основе выбора

Наш подход состоит в том, чтобы выполнить базовый поиск (например, поиск, который будет связан со списком или деревом) и обогатить его производным контентом. Обогащенный поиск — это то, что мы называем «Поиск на основе селектора». Поиск, который в конечном итоге будет использоваться контекстно-зависимыми действиями, является объединением базового поиска. Поиск объединения содержит как содержимое основного поиска, так и производное содержимое.

Поиск на основе селектора является ключом к расширяемости в нашем подходе. Любой модуль может добавлять новые «селекторы» в поиск на основе селектора. Каждый селектор отвечает за интерпретацию текущего выделения (содержимого базового поиска) и, возможно, за создание производных объектов, которые мы называем «Задачи» в качестве соглашения. В любой момент содержимое поиска на основе селектора — это просто сочетание задач, возвращаемых всеми селекторами.

Делая шаг назад, мы можем видеть селекторы (или поиск на основе селекторов) как слой адаптации. Этот уровень отвечает за адаптацию / преобразование / преобразование базового поиска в форму, которая может быть интерпретирована действиями. Материализация этого адаптационного слоя позволяет сделать его очень расширяемым, как в предлагаемом подходе.

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

 

Фрагменты кода

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

  • единственный вид Задачи: MessageTask, который просто инкапсулирует java.lang.String;
  • два контекстно-зависимых действия для этой задачи:

    • действие MessagesAsDialog, которое открывает диалоговое окно, отображающее все сообщения (из всех текущих MessageTasks),
    • действие WriteMessagesToStderr, которое записывает сообщения в стандартный вывод ошибок.
  • два селектора, производящие такие задачи:

    • универсальный селектор DefaultHelloMessage, который просто создает MessageTask, передающий привет любому выбранному клиенту,
    • более конкретный селектор «SalutMessage», создающий дополнительное сообщение, рассуждая о городе выбранного Клиента.

Код для задач, селекторов и действий

Никаких ограничений не накладывается на задачи, здесь это простой Java-объект:

public class MessageTask {
    private final String text;
    public MessageTask(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
}
            

Создать новый селектор действительно просто, используя удобный базовый класс AbstractSelector.

public class DefaultHelloMessageSelector extends AbstractSelector<Customer> {
    public DefaultHelloMessageSelector() {
        super(Customer.class);
    }
    protected void getTasks(ArrayList result, Collection<Customer> context) {
        for (Customer customer : context) {
            result.add(new MessageTask("From DefaultHelloMessageSelector:    Hi "
                                       + customer.getCustomerName()));
        }
    }
}
            

Затем селектор нужно зарегистрировать в файле layer.xml. Место для регистрации зависит от того, где настроен наш компонент «Список клиентов». Вот оно / Демо / Селекторы:

<filesystem>
    <folder name="Demo">
        <folder name="Selectors">
            <file name="fr-prima-...-DefaultHelloMessageSelector.instance"/> </folder> </folder> </filesystem>

Действия — это обычные контекстно-зависимые действия. Они просто используют задачи в качестве контекста, здесь MessageTask. Как и другие действия, они также должны быть соответствующим образом зарегистрированы в файле layer.xml. Вот действие, которое открывает диалоговое окно:

public final class MessagesAsDialog implements ActionListener {
    private final List<MessageTask> context;
    public MessagesAsDialog(List<MessageTask> context) {
        this.context = context;
    }
    public void actionPerformed(ActionEvent ev) {
        StringBuilder msg = new StringBuilder();
        for (MessageTask messageTask : context) {
            msg.append(messageTask.getText()).append("\n");
        }
        Message d = new Message("MessageAsDialog:\n\n" + msg);
        DialogDisplayer.getDefault().notify(d);
    }
}
            

Код, когда вы хотите создать поиск на основе селектора

Используя предоставленный модуль SelectorBasedLookup netbeans , поиск на основе селектора не только легко расширяется, но также и прост в создании. В примере это делается с помощью этого кода:

 Lookup base = ExplorerUtils.createLookup(em, getActionMap());
 // with classical lookup, just:
 //   associateLookup(base);

 // with Selector Based Lookup:
 Lookup sbl = SelectorBasedLookup.createLookup(base, "Demo/Selectors");
 associateLookup(new ProxyLookup(base, sbl));
            

Conclusions

This article has proposed an architectural pattern for fully decoupling actions and “action enabling strategies”. This pattern is illustrated with the NetBeans Platform Lookup API. A ready to use NetBeans module is available if you want to apply this method with minimal implementation effort.

Any questions? Something not clear? Any comments?