Я столкнулся с липкой проблемой, которую я не могу решить с помощью дженериков Java. Это немного сложно, но я не мог придумать более простой сценарий, чтобы проиллюстрировать проблему... Вот:
У меня есть класс Processor, для которого требуется Context. Существуют разные типы контекста; большинству процессоров просто нужен любой абстрактный контекст, но другим требуется определенный подкласс. Как это:
abstract class AbstractProcessor<C extends Context> {
public abstract void process(C context);
}
class BasicProcessor extends AbstractProcessor<Context> {
@Override
public void process(Context context) {
// ... //
}
}
class SpecificProcessor extends AbstractProcessor<SpecificContext> {
@Override
public void process(SpecificContext context) {
// ... //
}
}
Хорошо, круто: процессоры могут объявить тип контекста, который им нужен, и они могут предположить, что правильный тип будет передан в process() без приведения.
Теперь у меня есть класс Dispatcher, которому принадлежит сопоставление строк с процессорами:
class Dispatcher<C extends Context> {
Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();
public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
processorMap.put(name, processor);
}
public void dispatch(String name, C context) {
processorMap.get(name).process(context);
}
}
Хорошо, пока все хорошо! Я могу создать диспетчер для определенного типа контекста, а затем зарегистрировать пакет процессоров, которые могут ожидать любую абстракцию этого типа контекста.
Теперь вот проблема: я хочу, чтобы абстрактный тип Context владел Dispatcher, а производные типы Context должны иметь возможность регистрировать дополнительные процессоры. Вот самое близкое, что я мог найти к рабочему решению, но оно не работает полностью:
class Context<C extends Context> {
private final Dispatcher<C> dispatcher = new Dispatcher<C>();
public Context() {
// every context supports the BasicProcessor
registerProcessor("basic", new BasicProcessor());
}
protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
dispatcher.registerProcessor(name, processor);
}
public void runProcessor(String name) {
dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
}
}
// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
public SpecificContext() {
// the SpecificContext supports the SpecificProcessor
registerProcessor("specific", new SpecificProcessor());
}
}
Проблема в том, что мне нужно объявить универсальный Dispatcher в базовом классе Context, но я хочу, чтобы переменная типа ссылалась на конкретный производный тип для каждого подтипа Context. Я не вижу способа сделать это без дублирования некоторого кода в каждом подклассе Context (в частности, конструкции Dispatcher и метода registerProcessor). Вот что я думаю, что я действительно хочу:
Dispatcher<MyRealClass> dispatcher = new Dispatcher<MyRealClass>();
Есть ли способ объявить общий тип объекта с типом ПОДКЛАССА объявляющего класса?
Да, я могу решить эту проблему с помощью небольшого приведения с низким уровнем риска, так что это в основном академический вопрос... Но я хотел бы найти решение, которое просто работает сверху донизу! Вы можете помочь? Как бы вы подошли к этой архитектуре?
ОБНОВЛЕНИЕ:
Вот полный исходный код, обновленный, чтобы включить предложение Анджея Дойла использовать <C extends Context<C>>
; все равно не работает, потому что Context<C> != C
:
class Context<C extends Context<C>> {
private final Dispatcher<C> dispatcher = new Dispatcher<C>();
public Context() {
// every context supports the BasicProcessor
registerProcessor("basic", new BasicProcessor());
}
protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
dispatcher.registerProcessor(name, processor);
}
public void runProcessor(String name) {
dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
}
}
// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
public SpecificContext() {
// the SpecificContext supports the SpecificProcessor
registerProcessor("specific", new SpecificProcessor());
}
}
abstract class AbstractProcessor<C extends Context<C>> {
public abstract void process(C context);
}
class BasicProcessor extends AbstractProcessor {
@Override
public void process(Context context) {
// ... //
}
}
class SpecificProcessor extends AbstractProcessor<SpecificContext> {
@Override
public void process(SpecificContext context) {
// ... //
}
}
class Dispatcher<C extends Context<C>> {
Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();
public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
processorMap.put(name, processor);
}
public void dispatch(String name, C context) {
processorMap.get(name).process(context);
}
}