Статьи

Адаптеры Eclipse — практическое объяснение

При программировании плагинов Eclipse вы быстро сталкиваетесь с адаптерами Eclipse. Если вы не знакомы с шаблоном адаптера, адаптеры могут привести к путанице. Адаптеры Eclipse на самом деле очень просты, и я надеюсь сделать их еще проще в этой статье.

Адаптеры работают с простой предпосылкой: учитывая некоторый адаптируемый объект A, найдите мне соответствующий объект типа B для него. Интерфейс адаптера Eclipse показан в листинге 1. Интерфейс возвращает объект, который является экземпляром данного класса, связанного с объектом, или возвращает значение null, если такой связанный объект не может быть найден.

package org.eclipse.core.runtime;
public interface IAdaptable {
public Object getAdapter(Class adapter);
}

Листинг 1: Интерфейс адаптера Eclipse 

Например, если бы я хотел перейти от яблок к апельсинам, я бы сделал что-то вроде листинга 2. В этом примере IApple расширяет интерфейс IAdaptable.

IApple macintosh = new Macintosh();
IOrange orange = (IOrange) macintosh.getAdapter(IOrange.class);
if (orange==null)
log("No orange");
else
log("Created a "+ orange.getClass().getCanonicalName()); 

Листинг 2: Адаптация от яблок к апельсинам 

Одним из основных применений адаптеров является отделение кода модели от кода вида, как в контроллере модель-представление или шаблон-модель представления. Мы бы не хотели помещать информацию о презентации в виде иконок в нашу модель Apple. Мы бы еще один класс, чтобы справиться, как представить это. Мы могли бы сделать это с помощью адаптеров, как показано в листинге 3.

IApple apple = new Macintosh();
ILableProvider label = (ILabelProvider)apple.getAdapter(ILabelProvider.class);
String text = label.getText(apple);

Листинг 3: Использование адаптеров для отделения модели от вида. 

Адаптеры позволяют нам преобразовывать объекты в другие цели, которые объекты не должны были предвидеть. Например, объектам apple в листинге 3 не нужно ничего знать о поставщике меток. Мы можем реализовать метод getAdapter () вручную для каждого объекта apple, но это не позволит достичь цели. Вместо этого мы должны отложить адаптацию к платформе, как показано в листинге 4.

public abstract class Fruit implements IAdaptable{
public Object getAdapter(Class adapter){
return Platform.getAdapterManager().getAdapter(this, adapter);
}
}

Листинг 4: Адаптация через платформу

Адаптер Фабрики

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

package com.jeffricker.fruit;
import org.eclipse.core.runtime.IAdapterFactory;
import com.jeffricker.fruit.apples.IApple;
import com.jeffricker.fruit.apples.Macintosh;
import com.jeffricker.fruit.oranges.IOrange;
import com.jeffricker.fruit.oranges.Mandarin;
/**
* Converts apples to oranges
* @author Ricker
*/
public class OrangeAdapterFactory implements IAdapterFactory {
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IOrange.class) {
if (adaptableObject instanceof Macintosh) {
return new Mandarin();
}
}
return null;
}
public Class[] getAdapterList() {
return new Class[]{ IOrange.class };
}
}

Листинг 5: Фабрика адаптеров «Яблоки с апельсинами» 

В листинге 5 показана фабрика адаптеров, которая превращает яблоки в апельсины. Фабрика включает поведение, показанное ранее в листинге 2. Мы подробно опишем его поведение.

  • AdaptableObject — это объект, с которого мы начинаем, яблоко. AdaptableObject всегда является экземпляром объекта.

  • Адаптеры — это объект, к которому мы адаптируемся, оранжевый. AdapterType всегда является типом класса, а не экземпляром объекта

  • AdapterType всегда является типом класса, а не экземпляром объекта. Список адаптеров — это список типов классов, к которым эта фабрика может адаптировать объекты. В данном случае это только апельсины.

Мы должны зарегистрировать фабрику адаптеров на платформе Eclipse, чтобы она была полезной. В листинге 6 показана запись регистрации из файла манифеста плагина. Точка расширения — org.eclipse.core.runtime.adapters. Здесь я обычно путаюсь, поэтому обратите внимание. AdaptableType — это то, от чего мы адаптируемся. В данном случае это яблоки. Адаптер — это то, к чему мы адаптируемся. В данном случае это апельсины.

<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="com.jeffricker.fruit.apples.IApple"
class="com.jeffricker.fruit.OrangeAdapterFactory">
<adapter
type="com.jeffricker.fruit.oranges.IOrange">
</adapter>
</factory>
</extension>

Листинг 6: Регистрация фабрики адаптеров

Для фабрики может быть несколько записей адаптера. Адаптеры, перечисленные в точке расширения, должны быть такими же, как те, которые предоставляются методом getAdapterList () на фабрике адаптеров. Если мы посмотрим на списки вместе и проследим логику, адаптеры начнут иметь смысл.

  1. Мы создаем экземпляр объекта Macintosh.
    IApple macintosh = new Macintosh();
    
  2. Мы просим Macintosh адаптироваться к IOrange
    IOrange orange = (IOrange) macintosh.getAdapter(IOrange.class);
    
  3. Объект Macintosh направляет запрос на платформу
    public Object getAdapter(Class adapter){
    return Platform.getAdapterManager().getAdapter(this, adapter);
    }
    
  4. Платформа находит соответствующую фабрику адаптеров через
    реестр.
    <factory
    adaptableType="com.jeffricker.fruit.apples.IApple"
    class="com.jeffricker.fruit.OrangeAdapterFactory">
    <adapter type="com.jeffricker.fruit.oranges.IOrange"/>
    </factory>
    
  5. Платформа вызывает фабричный метод, передавая ему экземпляр
    объекта Macintosh и тип класса IOrange.
  6. Фабрика адаптеров создает экземпляр объекта Mandarin
    public Object getAdapter(Object adaptableObject, Class adapterType) {
    if (adapterType == IOrange.class) {
    if (adaptableObject instanceof Macintosh) {
    return new Mandarin();
    }
    }
    return null;
    }
    

У меня возникает путаница с параметром adaptableType в точке расширения. У нас нет того, что указано в заводском интерфейсе адаптера. Он скрыт в логике фабричного метода getAdapter (). Его присутствие в реестре имеет смысл, когда вы думаете об этом.
Мы просим платформу найти адаптер для объекта Macintosh. Фабрики должны как-то быть связаны с иерархией классов Macintosh. В нашем случае завод зарегистрирован в IApples. На рисунке 1 показана связь между объявлениями в реестре точек расширения и классом фабрики адаптеров.

[img_assist | nid = 2268 | title = Рисунок 1: Связь между фабрикой и точкой расширения | desc = | link = none | align = undefined | width = 545 | height = 437]


Пример провайдера презентаций

Адаптация яблок к апельсинам, конечно, глупый пример, но я мог бы расширить этот пример на что-то более актуальное. В листинге 3 я показал адаптацию объекта apple к ILabelProvider, интерфейсу, используемому виджетами JFace для представления. Фабрика для этой работы показана в листинге 7. Регистрация показана в листинге 8, а эскиз поставщиков показан в листинге 9.

Если вы посмотрите на классы провайдеров, сгенерированные Eclipse Modeling Framework (EMF), вы увидите, что концепция этого примера приняла логическое завершение.

package com.jeffricker.fruit.provider;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import com.jeffricker.fruit.apples.IApple;
import com.jeffricker.fruit.oranges.IOrange;
public class FruitProviderAdapterFactory implements IAdapterFactory {
private AppleProvider appleProvider;
private OrangeProvider orangeProvider;
/** The supported types that we can adapt to */
private static final Class[] TYPES = {
FruitProvider.class, ILabelProvider.class, IContentProvider.class };
public Object getAdapter(Object adaptableObject, Class adapterType) {
if ((adapterType == FruitProvider.class)||
(adapterType == ILabelProvider.class)||
(adapterType == IContentProvider.class)){
if (adaptableObject instanceof IApple)
return getAppleProvider();
if (adaptableObject instanceof IOrange)
return getOrangeProvider();
}
return null;
}
public Class[] getAdapterList() {
return TYPES;
}
protected AppleProvider getAppleProvider(){
if (appleProvider == null)
appleProvider = new AppleProvider();
return appleProvider;
}
protected OrangeProvider getOrangeProvider(){
if (orangeProvider == null)
orangeProvider = new OrangeProvider();
return orangeProvider;
}
}

Листинг 7: Фабрика провайдера фруктов для отображения фруктов в виджете JFace

<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="com.jeffricker.fruit.IFruit"
class="com.jeffricker.fruit.provider.FruitProviderAdapterFactory">
<adapter
type="com.jeffricker.fruit.provider.FruitProvider">
</adapter>
<adapter
type="org.eclipse.jface.viewers.IContentProvider">
</adapter>
<adapter
type="org.eclipse.jface.viewers.ILabelProvider">
</adapter>
</factory>
</extension>

Листинг 8: Регистрация фабрики адаптеров поставщика фруктов

package com.jeffricker.fruit.provider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
public abstract class FruitProvider implements
ILabelProvider, IContentProvider {
...
}
/**
* Provides the display of IApple objects
*/
public class AppleProvider extends FruitProvider{
public String getText(Object element){
...
}
public Image getIcon(Object element){
...
}
}
/**
* Provides the display of IOrange objects
*/
public class OrangeProvider extends FruitProvider {
...
}

Листинг 9: Классы провайдера

Пример из реального мира

Eclipse Communication Framework (ECF) начиналась как средство для размещения обмена мгновенными сообщениями на платформе Eclipse, но с тех пор расширила сферу действия, чтобы включить несколько средств обмена данными в Eclipse Rich Client Platform (RCP).
ECF начинается с простого интерфейса, называемого контейнером, и использует шаблон IAdaptable Eclipse для достижения определенной функциональности. Если бы мы использовали ECF для обмена мгновенными сообщениями, мы бы сконцентрировались на адаптации контейнера для присутствия и других интерфейсов.

В листинге 10 показано, как создать контейнер ECF. Контейнер предоставляет универсальные средства для обработки любого типа протокола уровня сеанса. В листинге 11 показано, как адаптировать контейнер для управления присутствием, что является обычной функцией обмена мгновенными сообщениями. Шаблон контейнера-адаптера отделяет протоколы уровня сеанса от сервисов, предоставляемых по этим протоколам.

// make container instance
IContainer container = ContainerFactory.getDefault()
.createContainer("ecf.xmpp");
// make targetID
ID newID = IDFactory.getDefault()
.createID("ecf.xmpp","[email protected]");
// then connect to targetID with null authentication data
container.connect(targetID,null);

Перечисление 10, Создающее соединение ECF

IPresenceContainer presence = (IPresenceContainer)container
.getAdapter(IPresenceContainer.class);
if (presence != null) {
// The container DOES expose IPresenceContainer capabilities
} else {
// The container does NOT expose IPresenceContainer capabilities
}

Перечисление 11, Адаптирующее контейнер для функциональности

Возможности широкие. Например, мы можем создать наш собственный адаптер IMarketDataContainer, который обеспечивает потоковую передачу рыночных данных. Мы создали бы его так же, как IPresenceContainer. Как показано в листинге 12, разные поставщики рыночных данных могут иметь разные протоколы сеансового уровня, даже проприетарные протоколы, но шаблон containeradapter позволит нам подключить их все к нашей Eclipse RCP одинаковым образом.

IContainer container = ContainerFactory.getDefault()
.createContainer("md.nyse");
ID newID = IDFactory.getDefault().createID("md.nyse","[email protected]");
container.connect(targetID,null);
IMarketDataContainer marketData = (IMarketDataContainer)container
.getAdapter(IMarketDataContainer.class);

Перечисление 12, новые типы контейнера для ECF

Шаблон адаптера — это мощный инструмент, который вы найдете на всей платформе Eclipse. Я надеюсь, что с помощью практического объяснения в этой статье вы теперь сможете использовать эту мощь в своих собственных приложениях RCP.