Один из подходов - использовать Шаблон посетителя .Это будет выглядеть примерно так:
public interface MySuperInterface {
<T> T acceptVisitor(MySuperInterfaceVisitor<T>);
}
public interface MySuperInterfaceVisitor<T> {
T visitA(SubclassA a);
T visitB(SubclassB a);
}
public class SubclassA implements MySuperInterface {
<T> T acceptVisitor(MySuperInterfaceVisitor<T> visitor) {
return visitor.visitA(this);
}
}
public class SubclassB implements MySuperInterface {
<T> T acceptVisitor(MySuperInterfaceVisitor<T> visitor) {
return visitor.visitB(this);
}
}
public class MySuperHandler implements MySuperInterfaceVisitor<Foo>{
Foo visitA(SubclassA a) {
// construct Foo from SubclassA instance
}
Foo visitB(SubclassB a) {
// construct Foo from SubclassB instance
}
}
Это немного похоже на ваш # 2, за исключением того, что интерфейсу (и подклассам) не нужно знать об обработчике.Им просто нужно знать об интерфейсе посетителя.Это хорошо, если вы не хотите, чтобы MySuperInterface
и его реализации знали о ваших конкретных обработчиках.
Кстати, вместо вызова:
myHandler.handle(myImpl);
вы бы позвонили:
myImpl.acceptVisior(myHandler);
Этот подход хорош, если вы хотите убедиться, что каждый обработчик может обрабатывать каждую реализацию вашего интерфейса, но при этом помешать реализациям знать обо всех существующих «обработчиках».
Если вы добавите другую реализацию вашего интерфейса (MySuperInterface
), компилятор заставит вас добавить метод acceptVisitor
.Этот метод может использовать один из существующих visit*
методов, или вам придется пойти и добавить новый в интерфейс посетителя.Если вы делаете последнее, вы должны затем обновить все реализации посетителя (иначе говоря, «обработчик»).Это гарантирует, что каждый подтип может быть обработан, в дальнейшем.
Этот подход более сложен, чем тот, который содержится в ответе ассилиев, и имеет смысл, только если вы хотите разорвать связь между реализациями MySuperInterface
и ваш код обработчика, или у вас есть сильное желание организовать свой код обработчика так, чтобы весь код для определенного типа обработки был «вместе».
Одним из распространенных применений шаблона посетителя является рендеринг объектов вразличные пути.Предположим, вы хотите иметь возможность конвертировать объект в PDF или HTML.В вашем интерфейсе могут быть методы toHTML и toPDF.Недостатком этого подхода является то, что теперь ваши классы зависят от ваших библиотек для создания HTML и PDF.Кроме того, если кто-то позже захочет добавить новый тип вывода, он должен изменить эти основные классы, что может быть нежелательно.С шаблоном посетителя только о библиотеках посетителя нужно знать о библиотеках PDF или HTMl, и новые посетители могут быть добавлены без изменения основных классов.(Но опять же, добавление новых базовых классов означает, что вам нужно либо повторно использовать существующий метод visit*
, либо вам придется изменить все реализаций посетителя.)