Как правильно смешать дженерики и наследование, чтобы получить желаемый результат? - PullRequest
6 голосов
/ 12 июня 2010

Мой вопрос нелегко объяснить словами, к счастью, это не так сложно продемонстрировать. Итак, терпите меня:

public interface Command<R>
{
    public R execute();//parameter R is the type of object that will be returned as the result of the execution of this command
}

public abstract class BasicCommand<R> implements Command<R>
{
}

public interface CommandProcessor<C extends Command<?>>
{
    public <R> R process(C<R> command);//this is my question... it's illegal to do, but you understand the idea behind it, right?
}

//constrain BasicCommandProcessor to commands that subclass BasicCommand
public class BasicCommandProcessor<C extends BasicCommand<?>> implements CommandProcessor<C>
{
    //here, only subclasses of BasicCommand should be allowed as arguments but these
    //BasicCommand object should be parameterized by R, like so: BasicCommand<R>
    //so the method signature should really be 
    //    public <R> R process(BasicCommand<R> command)
    //which would break the inheritance if the interface's method signature was instead:
    //    public <R> R process(Command<R> command);
    //I really hope this fully illustrates my conundrum
    public <R> R process(C<R> command)
    {
        return command.execute();
    }
}

public class CommandContext
{
    public static void main(String... args)
    {
        BasicCommandProcessor<BasicCommand<?>> bcp = new BasicCommandProcessor<BasicCommand<?>>();
        String textResult = bcp.execute(new BasicCommand<String>()
        {
            public String execute()
            {
                return "result";
            }
        });
        Long numericResult = bcp.execute(new BasicCommand<Long>()
        {
            public Long execute()
            {
                return 123L;
            }
        });
    }
}

По сути, я хочу, чтобы универсальный метод "process" определял тип универсального параметра объекта Command. Цель состоит в том, чтобы иметь возможность ограничивать различные реализации CommandProcessor определенными классами, которые реализуют интерфейс Command, и в то же время иметь возможность вызывать метод процесса любого класса, который реализует интерфейс CommandProcessor, и заставить его возвращать объект типа, указанного Параметризованный командный объект. Я не уверен, что мое объяснение достаточно ясно, поэтому, пожалуйста, дайте мне знать, если необходимы дальнейшие объяснения. Я думаю, вопрос в том, можно ли вообще это сделать? Если ответ «Нет», то какой будет лучший обходной путь (я подумал о паре самостоятельно, но мне хотелось бы свежих идей)

Ответы [ 2 ]

3 голосов
/ 12 июня 2010

К сожалению, вы не можете сделать это. Поскольку вы хотите, чтобы интерфейс CommandProcessor был определен в терминах Command, ваша реализация должна быть готова принять любой тип Command экземпляра - обобщенные элементы не могут ограничить это значение BasicCommand - если это возможно, то BasicCommandProcessor подкласс не будет реализовывать интерфейс CommandProcessor.

Или, с другой стороны, с учетом интерфейса CommandProcessor, дженерики не могут гарантировать, что он вызывался только с BasicCommand экземплярами. Для этого нужно знать реализацию и идти вразрез с точки зрения полиморфизма и интерфейсов.

Вы можете параметризовать результат команды, но не конкретный класс команды.

public interface Command<R>
{
    public R execute();//parameter R is the type of object that will be returned as the result of the execution of this command
}

public abstract class BasicCommand<R> implements Command<R>
{
}

public interface CommandProcessor
{
    public <R> R process(Command<R> command);
}

public class BasicCommandProcessor implements CommandProcessor
{
    public <R> R processBasicCommand(BasicCommand<R> command)
    {
       return command.execute();
    }

    public <R> R process(Command<R> command)
    {
       return processBasicCommand((BasicCommand<R>)command);
    }
}

Самый простой подход - предоставить метод, который принимает определенный тип, который вам нужен, и вызвать его в универсальном методе. (См. BasicCommandProcessor выше.)

1 голос
/ 13 июня 2010

По сути, я хочу общий метод "process" для определения типа универсального параметра Команды объект.

Это противоречит идее определения команды в качестве параметра типа для включающего типа: при создании экземпляра CommandProcessor для C может быть указан фактический тип, такой как Command<String>. Можно было бы даже предоставить не универсальный тип, такой как

class Foo implements Command<String> {
    ...
}

Что тогда будет означать C<R>? Foo<R>? Command<String><R>? Command<R>?

Итак, какие у вас есть варианты? Если CommandProcessor нужно работать только с определенным типом возврата, вы можете сделать:

class CommandProcessor<R, C extends Command<R>> {
    R process(C command);
}

class FancyCommandProcessor<R, C extends FancyCommand<R>> extends CommandProcessor<R,C> {

}

Однако я подозреваю, что вы хотите, чтобы CommandProcessor работал со всем типом семейства команд. Это само по себе не будет проблемой, просто объявите:

<R> R process(FancyCommand<R> command);

Если, однако, вы дополнительно хотите установить связь подтипов между CommandProcessors для разных семейств команд, чтобы вы могли переопределить process, вы рискуете выйти за пределы выразительности обобщений Java. В частности, вам понадобится либо эквивалент параметров типа «шаблон» в C ++ (которые позволяют передавать шаблон в качестве фактического аргумента типа), либо возможность захвата параметра типа Command с учетом фактического аргумента типа, который известен как расширение Command , Java не поддерживает ни того, ни другого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...