В предыдущей статье я обсуждал, как создавать универсальные классы в 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 показана правильная реализация бета-интерфейса. С помощью этого объявления мы получаем все методы родительских классов. Мы также можем продолжать бесконечно с большим количеством наследства детей.