Реализовать цепочку ответственности с использованием дженериков

Итак, я хочу реализовать цепочку ответственности, но использовать дженерики с верхней границей.

public abstract class Handler<C extends Command> {

    private Handler<? extends Command> successor;

    public Handler(Handler<? extends Command> successor) {
        this.successor = successor;
    }

    public final String handle(C cmd) {
        if (canHandle(cmd)) {
            return doHandle(cmd);
        } else {
            // The method doHandle(capture#3-of ? extends Command) in the type Handler<capture#3-of ? extends Command>
            // is not applicable for the arguments (C)
            return successor.doHandle(cmd);
        }
    }

    protected abstract boolean canHandle(C cmd);

    protected abstract String doHandle(C cmd);

}

abstract class Command {
    public String getParamBase() {
        return "base";
    }
}

class CommandTypeOne extends Command {
    public String getTypeOneParam() {
        return "ParamTypeOne";
    }
}

class CommandTypeTwo extends Command {
    public String getTypeTwoParam() {
        return "ParamTypeTwo";
    }
}

Конечно, я мог бы иметь класс, не являющийся универсальным, и везде использовать Command в качестве параметра, и это сработало бы. Но я не хочу использовать приведение в методе doHandle для перехода к соответствующему подтипу Command. Я хочу иметь обработчик для каждого из подтипов, общий и просто связать их.

Проблема, которую я получаю: Метод doHandle(capture#3-of ? extends Command) в типе Handler неприменим для аргументов (C)

Почему, если C расширяет Command?


person dalvarezmartinez1    schedule 16.04.2015    source источник
comment
В какой строке у вас ошибка?   -  person gurghet    schedule 16.04.2015
comment
Нужно ли в этом случае использовать дженерики? Все объекты, которые расширяют Command, имеют отношение is-a к Command. Будет ли ваш Handler ожидать, что все Commands будут одного и того же подтипа?   -  person PaulProgrammer    schedule 16.04.2015
comment
Связанный вопрос: stackoverflow.com/ вопросы/1684121/   -  person Mick Mnemonic    schedule 16.04.2015
comment
@gurghet, вы тоже можете увидеть ошибку в коде, я также поместил ее туда в качестве комментария. PaulProgrammer в том то и дело, обработчики не одного подтипа, я хочу иметь по одному обработчику на каждый подтип, подтип будет передаваться как общий. Я хочу использовать универсальный, чтобы избежать использования instanceof и литья. Спасибо за ссылку, Мик прочитает.   -  person dalvarezmartinez1    schedule 16.04.2015


Ответы (3)


Представьте, что произойдет, если вы замените Command на Animal (суперкласс Cat и Dog).

Подпись ссылки successor говорит, что она может работать с некоторыми типами животных, скажем, только с кошками. С другой стороны, параметр cmd метода handle() также говорит, что обрабатываемая команда может относиться к любому другому подтипу Animal — здесь Dog может быть допустимым типом.

Вы видите, что ничто не гарантирует, что обработчик successor сможет управлять конкретным типом команды, поэтому компилятор несколько разозлился.

person Olivier Croisier    schedule 16.04.2015
comment
Преемник Handler‹Cat› = новый Handler‹Cat›(null); Хендлер‹Собака› dogHandler = новый Хендлер‹Собака›(преемник); Метод canHandle, doHandle, handle в DogHandler обрабатывает Dog. Метод canHandle, doHandle, handle в преемнике обрабатывает Cat Не уверен, что понимаю. - person dalvarezmartinez1; 16.04.2015
comment
Если ваш экземпляр объявлен как Handler<Dog>, переменная типа C будет Dog в сигнатуре метода handle(). Но если его поле successor имеет тип Handler<Cat>, у вас проблема. - person Olivier Croisier; 16.04.2015
comment
да но почему, они оба Животные. Я не вижу большой разницы между ‹? расширяет Command› и ‹T расширяет Command› - person dalvarezmartinez1; 16.04.2015
comment
Поскольку ваши сигнатуры типов не говорят, что я могу обрабатывать любое животное, они говорят, что я могу обрабатывать только один тип животных, и точный тип животного, объявленный Handler, может отличаться от типа его преемника. - person Olivier Croisier; 16.04.2015

Ваш преемник поля может содержать экземпляр, который на самом деле является Handler<CommandTypeOne>, потому что он может хранить все до тех пор, пока C extends Command. Сигнатура его метода doHandle будет String doHandle(CommandTypeOne cmd). Если вы затем вызовете successor.doHandle() в handle(), вы передадите параметр, который гарантированно будет только Command, но не CommandTypeOne.

Возможно, вам стоит взглянуть на PECS. Производитель расширяет - Потребитель супер. Вы можете погуглить. В вашем случае ваш метод doHandle является потребителем, и вы должны использовать супер вместо расширений.

В этом случае я, вероятно, обойдусь без дженерика. Поместите Command там, где C сейчас. Это позволяет передавать команды и подтипы в цепочку обработчиков. Чтобы позволить обработчику различать команды, вы можете установить идентификатор для команд, возможно, со строкой или целым числом. Или вы можете использовать instanceof, чтобы разрешить обработчику проверять наличие определенного подтипа команды. Оба решения не очень объектно-ориентированы.

На этом этапе ваша команда выглядит как контейнер данных с различным содержимым в зависимости от вашего подтипа.

person Robert Kühne    schedule 16.04.2015
comment
согнуть мой PECS? Я слышал об этом некоторое время назад, уже забыл, попробую разобраться. - person dalvarezmartinez1; 16.04.2015
comment
Я думаю, вы говорите то же самое, что говорит Оливье Круазье, и я прекрасно понимаю ваше объяснение. Но, немного почитав о PECS, я не понимаю, как правильно модифицировать код. Даже если я заставлю ошибку исчезнуть, обработчик «CommandTypeOne» не является подтипом обработчика «Command» и я не могу создать цепочку обработчиков... - person dalvarezmartinez1; 17.04.2015

Здесь ваш класс Handler

//In your case 'C extends Command' in class declaration will be enough 
public abstract class Handler<C extends Command> { 

private Handler<C> successor;

public Handler(Handler<C> successor) {
    this.successor = successor;
}

public final String handle(C cmd) {
    if (canHandle(cmd)) {
        return doHandle(cmd);
    } else {
        // The method doHandle(capture#3-of ? extends Command) in the type Handler<capture#3-of ? extends Command>
        // is not applicable for the arguments (C)
        return successor.doHandle(cmd);
    }
}

protected abstract boolean canHandle(C cmd);

protected abstract String doHandle(C cmd);

}

person Yaroslav Kovbas    schedule 16.04.2015
comment
Поправьте меня, если я ошибаюсь. Разве не этого я пытаюсь избежать? Допустим, у нас есть один обработчик‹CommandTypeOne›, это означает, что его преемник сможет обрабатывать только ‹CommandTypeOne› в соответствии с конструктором и сигнатурой поля преемника. - person dalvarezmartinez1; 16.04.2015
comment
Имеет ли смысл иметь Handler‹CommandTypeOne› с преемником, таким как Handler‹CommandTypeTwo›? Если цепочный запрос будет CommandTypeOne, то его должен обрабатывать только преемник типа Handler‹CommandTypeOne›, не так ли? Или я не совсем понимаю, что вы на самом деле хотите сделать. - person Yaroslav Kovbas; 16.04.2015
comment
Каждый обработчик должен обрабатывать подтип. Попробуйте создать класс HandlerTypeOne extends Handler‹CommandTypeOne› {......} теперь посмотрите на его конструктор. Он принимает Hander‹CommandTypeOne›, как вы говорите, что нет смысла иметь 2 обработчика, обрабатывающих один и тот же тип запроса. - person dalvarezmartinez1; 16.04.2015
comment
О, я наконец понял вашу точку зрения, и то, что вы собираетесь делать, невозможно с использованием java jenerics. - person Yaroslav Kovbas; 17.04.2015
comment
Думаю, я понял именно это, отвечая на ответ Спониро. Большое спасибо за ваше время, сэр. - person dalvarezmartinez1; 17.04.2015