В предыдущей статье я обсуждал, как создавать универсальные классы в Eclipse Modeling Framework (EMF). В этой статье я расскажу, как использовать эти знания для реализации шаблона декоратора в EMF. В процессе, мы будем продвигать наши знания дженериков немного дальше.
В некоторых случаях полезно или даже необходимо обернуть унаследованные классы Java, особенно Java-бины, в модель EMF. Например, вам может потребоваться отобразить информацию, предоставленную службой удаленного вызова методов Java (RMI). У вас нет доступа к исходному коду, только файлы JAR.
Я лично полагаюсь на EMF для управления отображением объектов в EMF. Честно говоря, кто хочет написать весь этот код в рамках EMF.Edit? Я бы предпочел щелкнуть правой кнопкой мыши на файле GENMODEL и двигаться дальше.
Я создал пример упаковки или декорирования на заданных объектах другим. В этом примере у нас есть устаревший интерфейс Java с именем Aramaic. Мы создадим модель EMF с именем Greek, которая обернет классы от арамейского.
В листинге 1 показаны арамейские интерфейсы. Я сохранил это как можно проще. Класс Алеф имеет атрибут фамилия. Класс Beth наследуется от Aleph и добавляет псевдоним атрибута.
package com.jeffreyricker.aramaic;
public interface Aleph {
public String getSurname();
}
public interface Beth extends Aleph {
public String getNickname();
}
Листинг 1
Предположим, что мы передали JAR этой реализации. Мы бы использовали мастер плагинов Eclipse для преобразования JAR в пакет OSGI.
Далее нам нужно создать нашу модель EMF, которая будет обернуть или украсить объекты, которые реализуют арамейские интерфейсы. Я хочу сохранить это как можно более чистым. Интерфейс EMF должен отражать, но не иметь ссылки на устаревшие интерфейсы. Результат показан в листинге 2.
/**
* @model
*/
public interface Alpha {
/**
* @model
*/
public String getName();
}
/**
* @model
*/
public interface Beta extends Alpha {
/**
* @model
*/
public String getNickname();
}
Перечисление 2
Фактическая упаковка скрыта в реализации. В греческих интерфейсах нет ссылок на арамейские классы.
Нам нужен базовый интерфейс для украшения. Вот где начинаются дженерики. Интерфейс Decorator, показанный в листинге 3, очень прост. Универсальный <D> — это класс, который будет упакован декоратором. Реализация интерфейса показана в листинге 4.
package com.jeffreyricker.greek.util;
public interface Decorator<D> {
D getDecorated();
void setDecorated(D decorated);
}
Перечисление 3
package com.jeffreyricker.greek.impl;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import com.jeffreyricker.greek.util.Decorator;
public class DecoratorImpl<D> extends EObjectImpl implements Decorator<D>{
private D decorated;
public D getDecorated() {
return decorated;
}
public void setDecorated(D decorated) {
this.decorated = decorated;
}
}
Листинг 4
Наши греческие модельные объекты будут наследоваться от этого класса DecoratorImpl. Первый декоратор Alpha показан в листинге 5.
package com.jeffreyricker.greek.impl;
import com.jeffreyricker.aramaic.Aleph;
import com.jeffreyricker.greek.Alpha;
public class AlphaImpl<D extends Aleph>
extends DecoratorImpl<D> implements Alpha{
public AlphaImpl(){
super();
}
public String getName() {
return getDecorated().getSurname();
}
}
Листинг 5
Обратите внимание, что метод getName выполняет вызов getDecorated (). GetSurname (). Дженерики делают это возможным. Объявление <D extends Aleph> позволяет анализатору знать, что класс, возвращаемый getDecorated, будет иметь метод getSurname.
Объявление класса может выглядеть немного странно.
class AlphaImpl<D extends Aleph> extends DecoratorImpl<D> implements Alpha
Типичная декларация, которую можно ожидать:
class AlphaImpl extends DecoratorImpl<Aleph> implements Alpha // OKAY, BUT NO INHERITANCE
Это, безусловно, будет работать для этого класса. Однако, что происходит, когда нам нужно наследовать BetaImpl?
class BetaImpl extends AlphaImpl<Beth> implements Beta // WRONG
Если вы попробуете это, вы получите сообщение об ошибке, в котором совершенно правильно указано, что AlphaImpl не является универсальным.
Вы можете попробовать следующее
class BetaImpl extends DecoratorImpl<Beth> implements Beta // WRONG It would compile, but then the following would fail. Beta beta = new BetaImpl(); beta.getName(); // FAIL. No such method The following also do not work. class AlphaImpl<D> extends DecoratorImpl<D> implements Alpha // WRONG class AlphaImpl<C extends D> extends DecoratorImpl<Aleph> implements Alpha // WRONG
Вероятно, есть еще дюжина других заявлений, которые вы можете придумать, которые не работают
package com.jeffreyricker.greek.impl;
import com.jeffreyricker.aramaic.Beth;
import com.jeffreyricker.greek.Beta;
public class BetaImpl<D extends Beth> extends AlphaImpl<D> implements Beta{
public String getNickname(){
return this.getDecorated().getNickname();
}
}
Листинг 6
Однако при правильных обобщенных декларациях все работает как положено. В листинге 6 показана правильная реализация бета-интерфейса. С помощью этого объявления мы получаем все методы родительских классов. Мы также можем продолжать бесконечно с большим количеством наследства детей.