Я провел некоторое исследование, пытаясь разработать структуру преобразования типов, которая предоставляет возможность преобразовывать экземпляры исходного класса (например, Foo) в экземпляры классов результатов (например, Bar или Baz). Платформа должна обеспечивать возможность использования разной логики преобразования (т. Е. Разных преобразователей) для одной и той же пары источника и результата. Он также должен быть расширяемым, то есть позволять добавлять новые преобразователи для новых и существующих пар источника и результата. Еще одно требование - это безопасность типов, то есть любая попытка преобразовать экземпляр некоторого исходного класса в экземпляр класса результата без преобразователя, реализующего соответствующую логику преобразования, должна привести к ошибке времени компиляции.
Я решил использовать шаблон посетителей с конвертерами в качестве посетителей и конвертируемыми классами в качестве элементов. Чтобы обеспечить расширяемость и безопасность типов, я решил использовать дженерики. Итак, первая реализация структуры преобразования, которую я сделал под влиянием какой-то статьи в Интернете (к сожалению, я потерял ссылку), была ...
Платформа преобразования с преобразователями с отслеживанием состояния
Вот основные интерфейсы фреймворка Converter и Convertable:
public interface Converter<V extends Converter<V,A>, A extends Convertable<V,A>> {
void convert(A convertable);
}
public interface Convertable<V extends Converter<V,A>, A extends Convertable<V,A>> {
void convertWith(V converter);
}
Дженерики делают реализацию Convertable
, принимают только реализации Converter
, которые могут их преобразовывать, и делают реализацию Converter
только для посещений реализаций Convertable
, которые они сделали для преобразования. Вот пример таких преобразователей:
interface FooConverter extends Converter<FooConverter,Foo> {
void convert(Foo convertable);
void convert(FooChild1 convertable);
void convert(FooChild2 convertable);
}
public class Foo2BarConverter implements FooConverter {
private Bar result;
public Bar getResult() {
return result;
}
@Override
public void convert(Foo convertable) {
this.result = new Bar("This bar's converted from an instance of Foo");
}
@Override
public void convert(FooChild1 convertable) {
this.result = new Bar("This bar's converted from an instance of FooChild1");
}
@Override
public void convert(FooChild2 convertable) {
this.result = new Bar("This bar's converted from an instance of FooChild2");
}
}
public class Foo2BazConverter implements FooConverter {
private Baz result;
public Baz getResult() {
return result;
}
@Override
public void convert(Foo convertable) {
this.result = new Baz("This baz's converted from an instance of Foo");
}
@Override
public void convert(FooChild1 convertable) {
this.result = new Baz("This baz's converted from an instance of FooChild1");
}
@Override
public void convert(FooChild2 convertable) {
this.result = new Baz("This baz's converted from an instance of FooChild2");
}
}
А вот несколько классов, которые можно преобразовать с помощью этих конвертеров:
public class Foo implements Convertable<FooConverter, Foo> {
@Override
public void convertWith(FooConverter converter) {
converter.convert(this);
}
}
public class FooChild1 extends Foo {
@Override
public void convertWith(FooConverter converter) {
converter.convert(this);
}
}
public class FooChild2 extends Foo {
@Override
public void convertWith(FooConverter converter) {
converter.convert(this);
}
}
Вот классы результатов, то есть Bar
и Baz
:
public class Bar {
private String message;
public Bar(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public class Baz {
private String message;
public Baz(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
А вот код, который проверяет эти преобразователи:
Foo fooObj = new Foo();
Foo fooChild1Obj = new FooChild1();
Foo fooChild2Obj = new FooChild2();
// converting to bar
Foo2BarConverter foo2BarConverter = new Foo2BarConverter();
fooObj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());
fooChild1Obj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());
fooChild2Obj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());
// converting to baz
System.out.println();
Foo2BazConverter foo2BazConverter = new Foo2BazConverter();
fooObj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());
fooChild1Obj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());
fooChild2Obj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());
и вывод, построенный с помощью этого кода
This bar's converted from an instance of Foo
This bar's converted from an instance of FooChild1
This bar's converted from an instance of FooChild2
This baz's converted from an instance of Foo
This baz's converted from an instance of FooChild1
This baz's converted from an instance of FooChild2
Взгляните на поле result
в Foo2BarConverter
и Foo2BazConverter
. Это главный недостаток реализации. Это делает конвертеры с сохранением состояния, что не всегда удобно. Пытаясь избежать этого недостатка, я разработал ...
Платформа преобразования без двойной отправки
Суть этой реализации состоит в том, чтобы параметризовать преобразователи с помощью классов результатов и вернуть результаты из метода convert
из Converter
и convertWith
из метода Convertable
. Вот как это выглядит в коде:
public interface Converter<A extends Convertable<A>,R> {
R convert(A convertable);
}
public interface Convertable<A extends Convertable<A>> {
<R> R convertWith(Converter<A,R> converter);
}
public interface FooConverter<R> extends Converter<Foo,R> {
@Override
R convert(Foo convertable);
R convert(FooChild1 convertable);
R convert(FooChild2 convertable);
}
public class Foo2BarConverter implements FooConverter<Bar> {
@Override
public Bar convert(Foo convertable) {
return new Bar("This bar's converted from an instance of Foo");
}
@Override
public Bar convert(FooChild1 convertable) {
return new Bar("This bar's converted from an instance of FooChild1");
}
@Override
public Bar convert(FooChild2 convertable) {
return new Bar("This bar's converted from an instance of FooChild2");
}
}
public class Foo2BazConverter implements FooConverter<Baz> {
@Override
public Baz convert(Foo convertable) {
return new Baz("This baz's converted from an instance of Foo");
}
@Override
public Baz convert(FooChild1 convertable) {
return new Baz("This baz's converted from an instance of FooChild1");
}
@Override
public Baz convert(FooChild2 convertable) {
return new Baz("This baz's converted from an instance of FooChild2");
}
}
public class Foo implements Convertable<Foo> {
@Override
public <R> R convertWith(Converter<Foo,R> converter) {
return converter.convert(this);
}
}
public class FooChild1 extends Foo {
@Override
public <R> R convertWith(Converter<Foo,R> converter) {
return converter.convert(this);
}
}
public class FooChild2 extends Foo {
@Override
public <R> R convertWith(Converter<Foo,R> converter) {
return converter.convert(this);
}
}
V
удален из Convertable
объявления, потому что наличие класса результата в объявлении Converter
фактически заставило бы нас параметризовать реализации Convertable
с классами результатов. Он привязывает каждую реализацию convertable к единственному классу результата, в который она может быть преобразована. Итак, convertWith
в Convertable
относится к полученным преобразователям с Converter<A,R>
интерфейсом. И вот в чем проблема. Теперь реализации Convertable
, вызывающие полученный преобразователь, всегда будут вызывать convert
, который определен в интерфейсе Converter
, а не convert
методы, которые отменяют его в реализациях Converter
. Другими словами, convert(FooChild1 convertable)
и convert(FooChild2 convertable)
в Foo2BarConverter
и Foo2BazConverter
никогда не будут вызваны. По сути, он убивает основное понятие паттерна Visitor - двойную отправку. Вот тестовый код ...
Foo fooObj = new Foo();
Foo fooChild1Obj = new FooChild1();
Foo fooChild2Obj = new FooChild2();
// converting to bar
Foo2BarConverter foo2BarConverter = new Foo2BarConverter();
System.out.println(fooObj.convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild1Obj.convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild2Obj.convertWith(foo2BarConverter).getMessage());
System.out.println();
// converting to baz
Foo2BazConverter foo2BazConverter = new Foo2BazConverter();
System.out.println(fooObj.convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild1Obj.convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild2Obj.convertWith(foo2BazConverter).getMessage());
и его вывод, который демонстрирует, что методы переопределения не вызываются в этой реализации.
This bar's converted from an instance of Foo
This bar's converted from an instance of Foo
This bar's converted from an instance of Foo
This baz's converted from an instance of Foo
This baz's converted from an instance of Foo
This baz's converted from an instance of Foo
Следующая реализация, с которой я пытался сделать преобразователи без сохранения состояния, была ...
Конвертеры с параметризованными методами
Главное здесь - параметризовать только те методы, которые я хочу вернуть в результате преобразования, без параметризации деклараций интерфейсов.
public interface Converter<V extends Converter<V,A>, A extends Convertable<V,A>> {
<R> R convert(A convertable);
}
public interface Convertable<V extends Converter<V,A>, A extends Convertable<V,A>> {
<R> R convertWith(V converter);
}
interface FooConverter extends Converter<FooConverter,Foo> {
<R> R convert(Foo convertable);
<R> R convert(FooChild1 convertable);
<R> R convert(FooChild2 convertable);
}
public class Foo2BarConverter implements FooConverter {
@Override
public Bar convert(Foo convertable) {
return new Bar("This bar's converted from an instance of Foo");
}
@Override
public Bar convert(FooChild1 convertable) {
return new Bar("This bar's converted from an instance of FooChild1");
}
@Override
public Bar convert(FooChild2 convertable) {
return new Bar("This bar's converted from an instance of FooChild2");
}
}
public class Foo2BazConverter implements FooConverter {
@Override
public Baz convert(Foo convertable) {
return new Baz("This baz's converted from an instance of Foo");
}
@Override
public Baz convert(FooChild1 convertable) {
return new Baz("This baz's converted from an instance of FooChild1");
}
@Override
public Baz convert(FooChild2 convertable) {
return new Baz("This baz's converted from an instance of FooChild2");
}
}
public class Foo implements Convertable<FooConverter, Foo> {
@Override
public <R> R convertWith(FooConverter converter) {
return converter.convert(this);
}
}
public class FooChild1 extends Foo {
@Override
public <R> R convertWith(FooConverter converter) {
return converter.convert(this);
}
}
public class FooChild2 extends Foo {
@Override
public <R> R convertWith(FooConverter converter) {
return converter.convert(this);
}
}
Код тестирования
Foo fooObj = new Foo();
Foo fooChild1Obj = new FooChild1();
Foo fooChild2Obj = new FooChild2();
// converting to bar
Foo2BarConverter foo2BarConverter = new Foo2BarConverter();
System.out.println(fooObj.<Bar>convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild1Obj.<Bar>convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild2Obj.<Bar>convertWith(foo2BarConverter).getMessage());
System.out.println();
// converting to baz
Foo2BazConverter foo2BazConverter = new Foo2BazConverter();
System.out.println(fooObj.<Baz>convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild1Obj.<Baz>convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild2Obj.<Baz>convertWith(foo2BazConverter).getMessage());
и это вывод
This bar's converted from an instance of Foo
This bar's converted from an instance of FooChild1
This bar's converted from an instance of FooChild2
This baz's converted from an instance of Foo
This baz's converted from an instance of FooChild1
This baz's converted from an instance of FooChild2
На первый взгляд выглядит великолепно. Но на самом деле это решение небезопасно. Например, следующий вызов
fooObj.<Baz>convertWith(foo2BarConverter).getMessage()
не вызовет ошибку времени компиляции. Но это привело бы к ClassCastException во время выполнения.
Итак, общий вопрос следующий.
Есть ли способ сделать посетителя с обобщенным типом без сохранения состояния безопасным с помощью Java?
UPD: я добавил ссылки на источники всех трех реализаций: 1st, 2-й и 3-й