Один подход заключается в использовании Visitor Pattern. Это будет выглядеть примерно так:
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
и его реализации знали о ваших конкретных обработчиках.
BTW, вместо вызова:
myHandler.handle(myImpl);
вы бы назвали:
myImpl.acceptVisior(myHandler);
Этот подход хорош, если вы хотите, чтобы гарантировать, что каждый обработчик может обрабатывать каждую реализацию своего интерфейса, но все еще не позволяют реализациям узнать обо всех «обработчиках», которые существуют.
Если вы добавите еще одну реализацию своего интерфейса (MySuperInterface
), компилятор заставит вас добавить метод acceptVisitor
. Этот метод может либо использовать один из существующих методов visit*
, либо вам нужно будет добавить и добавить новый интерфейс посетителя. Если вы сделаете последнее, вы должны затем обновить все реализации посетителя (ака «обработчик»). Это гарантирует, что каждый подтип можно обработать, продвигаясь вперед.
Этот подход более сложный, чем тот, который отвечает на вопрос assylias, и имеет смысл только в том случае, если вы либо хотите разбить связь между реализациями MySuperInterface
и кодом вашего обработчика, либо у вас есть сильное желание организовать код вашего обработчика так что весь код для определенного типа обработки «вместе».
Одно общее использование шаблона посетителя - это рендеринг объектов по-разному. Предположим, вы хотите преобразовать объект в PDF или HTML. В вашем интерфейсе можно использовать метод toHTML и toPDF. Недостатком этого подхода является то, что теперь ваши классы зависят от ваших библиотек для генерации HTML и PDF. Кроме того, если кто-то позже хочет добавить новый тип вывода, им необходимо изменить эти основные классы, что может быть нежелательным. С шаблоном посетителя только классы vistior должны знать о библиотеках PDF или HTMl, а новые посетители могут быть добавлены без изменения основных классов. (Но опять же, добавление новых основных классов означает, что вам нужно либо повторно использовать существующий метод visit*
, либо вам придется изменить все вариантов реализации посетителя.)
Если вы принимаете интерфейс в качестве параметра, но все еще нужно знать точный тип за этим интерфейсом, то, возможно, в вашем интерфейсе отсутствуют методы? – MaDa
Спасибо. Я думаю, что добавление некоторых других методов в мой интерфейс (например, isA() или isB()) приведет к решению switch/case, которое я бы хотел избежать. также я не думаю, что это задача интерфейса по уходу за обработкой в классе обработчика. Или я неправильно понял? – zersaegen