Статьи

Идиомы платформы NetBeans: подключаемый верхний компонент (часть 1)


С частью моего проекта платформы NetBeans
mavenized , я был в состоянии перезапустить кодирование и рефакторинг — Есть много вещей , которые я написал в прошлом году за
Bluemarine , что после очистки, будет являться предметом моих постов в будущем.

Итак, я возобновляю серию статей об идиомах для платформы NetBeans , которую я приостановил сразу после первой публикации. Сегодня мы видим Pluggable TopComponent . Как обычно, здесь нет особого изобретения, но используются хорошо известные шаблоны, специально адаптированные для контекста платформы NetBeans.

Идея, конечно же, заключается в повторном использовании и модульности. Я проиллюстрирую некоторый реальный код, взятый из forceTen, который реализует функции компонента дерева, которые вы можете видеть на этом скриншоте:

Компонент с деревом называется «GeoExplorer» и позволяет просматривать и выбирать узлы, представляющие географические объекты; когда вы выбираете один, рендерер карт (называемый «GeoViewer») центрируется на нем. Два компонента разъединяются с помощью другой Идиомы Платформы, «Шины событий» : всякий раз, когда в GeoExplorer выбирается гео-объект, событие публикуется в Шине событий и принимается Geo Viewer.

 

 

EventBus

 

Поскольку я считаю Event Bus довольно полезной идиомой для интеграции различных компонентов, дизайн GeoExplorer имеет смысл для моих проектов; но в более широком контексте повторного использования вы можете захотеть сделать что-то другое. Есть также много других деталей, которые можно сделать немного по-другому: например, хотите ли вы, чтобы выбор происходил, когда вы нажимаете на узел одним щелчком мыши? Или когда вы дважды щелкните? Или вы предпочитаете всплывающее контекстное действие? Вам нужно реализовать взаимодополняющие поведения, такие как изменение заголовка главного окна в функции выбранного окна или узла и т. Д.? Как вы хотите загрузить данные в TopComponent, например, из базы данных или удаленного сервиса? В нетерпении, то есть во время инициализации или лениво, когда компонент отображается в первый раз?

Любой из этих вариантов имеет смысл в другом контексте, и действительно многократно используемый GeoExplorer должен позволять программисту использовать его «из коробки», добавляя только код интеграции и / или изменения конфигурации. Цель этой статьи — найти хороший дизайн, который позволит это сделать.

Предпосылки

Эта статья является первой в серии — это введение легко прочитать любому, кто обладает базовыми знаниями о платформе NetBeans; Следующие части будут иметь полные примеры кода, и я предполагаю, что вы хорошо разбираетесь в некоторых фундаментальных вещах:

 

Вступление

Глядя на то, как я определил свои требования, становится ясно, что я придерживаюсь модульного подхода: ну, MVC — это не решение. Вы удивлены? В то время как MVC является «отцом всех шаблонов», связанных с приложениями с уровнем представления, и хорошо справляется с отделением Модели от Представления и Контроллера, к сожалению, он монолитный: в частности, Контроллер — это единственный класс, который реализует все поведение и его трудно настроить.


Несколько месяцев назад, когда я начал работать с плагином Pluggable TopComponent, я просто думал о «MVC на стероидах», специально адаптированном для API-интерфейсов NetBean, в основном работающем над декомпозицией контроллера. Затем мне были доведены два сообщения о
DCI — данные, контекст, взаимодействие .
Бывший от прошлого марта в Artima и последний является
скринкастЯрда Тулач и Гертьян Виленга, которые описывают отношения между DCI и API-интерфейсами NetBeans. До сих пор я только мог смотреть скринкаст Ярды и все еще выясняю, что такое DCI. Я думаю, что Pluggable TopComponent в некоторых частях очень похож на «DCI для NetBeans», так как он в значительной степени основан на использовании Lookup в качестве инфраструктуры интеграции, но мои нынешние ограниченные знания DCI не позволяют мне говорить что-либо определенное. Тем не менее, пост Ярды повлиял на меня и помог мне понять, что я действительно ушел из MVC; таким образом, я полностью отбросил любую ссылку на MVC для имен участников идиомы.

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

Сколько разного поведения мы будем определять? Чтобы дать ответ на этот вопрос, мы должны сосредоточиться на ролях и обязанностях в классическом MVC.

 

Есть тонкий момент: если мы посмотрим на описание ролей и обязанностей MVC, мы обнаружим лишь упрощение реальности — это нормально, так как шаблоны — это упрощенная модель, сфокусированная на самых важных вещах. В настольном приложении многие вещи скрываются за ответственностью «Выбор вида для ответа»; например, оглядываясь на примеры из моего вступления, меняя заголовок окна, когда TopComponent получает фокус. Вообще говоря, мы должны думать о вещах, связанных с семантикой пользовательского интерфейса (например, захват / фокус, перетаскивание) и их конкретной реализацией (например, Swing, платформа NetBeans). Например, TopComponent имеет специальную функцию под названием «активация узла» — цитирование документации :

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

Выбор может быть установлен с помощью TopComponent.setActivationNodes (…) .

Если вы не вызовете setActivationNodes (), многие функции платформы не будут работать. 

И последнее, но не менее важное: хотя Модель может поддерживать представление в актуальном состоянии с помощью обычного шаблона Observer (который легко реализуется с помощью связанных свойств JavaBeans и API-интерфейсов Nodes), нам все же необходимо иметь дело с инициализацией, которую можно выполнить с помощью различные стратегии, такие как нетерпеливый, ленивый и т. д. И давайте зададим такие вопросы, как: синхронно или асинхронно связан наш источник данных? Имеет ли смысл функция «перезагрузки»? Управляемый пользователем или автоматизированный, с циклом опроса?

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

Из этого анализа выясняется, что наше поведение может происходить из разных областей: не только ориентированных на управление, но также ориентированных на пользовательский интерфейс, ориентированных на строителей и т. Д. Характерная особенность Pluggable TopComponent состоит в том, что независимо от их происхождения поведения разделяют одинаковую структуру и одинаковый способ сотрудничества.

Поведение

Теперь мы можем начать смотреть на некоторый код. Во-первых, как мы определяем поведение? Ну, это должен быть общий класс, который может делать все что угодно . Единственное требование, которое мы ставим, это то, что он может получить доступ к Lookup, обеспечивающему контекст. Таким образом, любое поведение может искать другие ресурсы и поведения, с которыми можно сотрудничать. Первой попыткой может быть определение базового абстрактного класса:

package it.tidalwave.netbeans.behaviour;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.openide.util.Lookup;

public abstract class Behaviour implements Lookup.Provider
{
@CheckForNull
private Lookup lookup;

public void setLookup (@Nonnull Lookup lookup)
{
this.lookup = lookup;
}

@Override
@Nonnull
public Lookup getLookup()
{
if (lookup == null)
{
throw new IllegalStateException("Not initialized!");
}

return lookup;
}

public void initialize()
{
}
}

Итак, в основном Поведение — это инъекционный Lookup.Provider. В качестве альтернативы можно полагаться на глобальный контекст, такой как Utilities.actionsGlobalContext ()) — я думаю, что Ярда использует глобальный контекст в своем посте. Мне не нравится такой подход, поскольку я вижу серьезное ограничение: так как Lookup запрашивается классом, вы не можете различить экземпляры одного и того же класса. Другими словами, что если мне понадобятся два разных GeoExplorers в моем приложении, каждый со своим конкретным набором экземпляров поведения? Это не может работать с глобальным контекстом, но может быть сделано, если контекст предоставляется Lookup для каждого TopComponent.

Простое, конкретное поведение может быть:

public class MyBehaviour
{
public void doSomething()
{
getLookup().lookup(AnotherService.class).doSomething2();
}

public void initialize()
{
// some initialization stuff
}
}

Но в 2009 году мы действительно хотим наследовать от базового класса? Все используют POJO, и я так хочу. Таким образом, я могу ожидать, что мои конкретные Поведения выглядят скорее как:

import javax.annotation.Resource;
import javax.annotation.PostConstruct;

public class MyBehaviour
{
@Resource
private AnotherService anotherService;

public void doSomething()
{
anotherService.doSomething2();
}

@PostConstruct
private void initialize()
{
// some initialization stuff
}
}

Это аннотации Java 6 с согласованной семантикой в ​​Java 6 и Spring. Идея состоит в том, чтобы TopComponent Lookup работал как Spring BeanFactory для всех Поведений.

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

Тем временем весь код уже может быть извлечен из ForceTen:

hg clone https://kenai.com/hg/forceten~src
hg update -C dzone-20091012

и повторно используемые части будут доступны для OpenBlueSky, как только этот проект будет создан. В любом случае, артефакты Maven скоро будут доступны в репозитории, и я планирую создать скринкаст, чтобы показать вам, как легко использовать эти компоненты в вашем собственном приложении.