Пустое значение в качестве возвращаемого параметра - PullRequest
11 голосов
/ 24 июля 2009

У меня есть этот интерфейс:

public interface Command<T> {
    T execute(String... args);
}

он отлично работает для большинства применений. Но когда я пытаюсь смоделировать команду, которая имеет только побочные эффекты (например, без возвращаемого значения), у меня возникает желание написать:

public class SideEffectCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
        return null; // null is fine?
    }
} 

Это общая проблема? Существуют ли лучшие практики для моделирования Commands с и без возвращаемого значения?

Я пробовал с этим адаптером, но я думаю, что это не оптимально по нескольким причинам:

public abstract class VoidCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
       execute2(args);
       return null;
    }

    public abstract void execute2(String... args);
}

Ответы [ 8 ]

10 голосов
/ 24 июля 2009

Я хотел бы использовать Void явно. Легко увидеть, что происходит без участия другого класса. Было бы неплохо, если бы вы могли переопределить возврат Void с помощью voidInteger с помощью int и т. Д.), Но это не является приоритетом.

4 голосов
/ 12 августа 2009

Вот реализация из нескольких лучших миров.

// Generic interface for when a client doesn't care
// about the return value of a command.
public interface Command {
    // The interfaces themselves take a String[] rather
    // than a String... argument, because otherwise the
    // implementation of AbstractCommand<T> would be
    // more complicated.
    public void execute(String[] arguments);
}

// Interface for clients that do need to use the
// return value of a command.
public interface ValuedCommand<T> extends Command {
    public T evaluate(String[] arguments);
}

// Optional, but useful if most of your commands are ValuedCommands.
public abstract class AbstractCommand<T> implements ValuedCommand<T> {
    public void execute(String[] arguments) {
        evaluate(arguments);
    }
}

// Singleton class with utility methods.
public class Commands {
    private Commands() {} // Singleton class.

    // These are useful if you like the vararg calling style.
    public static void execute(Command cmd, String... arguments) {
        cmd.execute(arguments);
    }

    public static <T> void execute(ValuedCommand<T> cmd, String... arguments) {
        return cmd.evaluate(arguments);
    }

    // Useful if you have code that requires a ValuedCommand<?>
    // but you only have a plain Command.
    public static ValuedCommand<?> asValuedCommand(Command cmd) {
        return new VoidCommand(cmd);
    }

    private static class VoidCommand extends AbstractCommand<Void> {
        private final Command cmd;

        public VoidCommand(Command actual) {
            cmd = actual;
        }

        public Void evaluate(String[] arguments) {
            cmd.execute(arguments);
            return null;
        }
    }
}

С этой реализацией клиенты могут говорить о Command, если им не важно возвращаемое значение, и ValuedCommand<T> если вам нужна команда, которая возвращает определенное значение.

Единственная причина, по которой не стоит использовать Void прямо - это все неприглядные return null; операторы, которые вы будете вынуждены вставить.

4 голосов
/ 10 августа 2009

Это выглядит хорошо для меня. Как говорили другие, Void изначально был разработан для механизма отражения, но теперь он довольно часто используется в Generics для описания таких ситуаций, как ваша.

Еще лучше: Google в своей структуре GWT использует то же самоеидея в их примерах для обратного обратного вызова ( пример здесь ). Я говорю: если Google делает это, это должно быть по крайней мере хорошо ..:)

3 голосов
/ 10 августа 2009

Это не распространенная проблема. Проблема, которую вы должны решить - это ожидание вашего интерфейса. Вы комбинируете поведение интерфейса без побочных эффектов с интерфейсом, который позволяет побочные эффекты.

Учитывайте следующее:

public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();       
        reportResults(sec);     
    }   

    public static void reportResults(Command<?> cmd){

        final Object results = cmd.execute("arg");
        if (results != null ) {
            System.out.println(results.getClass());
        }
    }
}

Нет ничего плохого в использовании <Void> в качествеТип шаблона, но позволяющий смешивать его с реализациями для «Command <T>», означает, что некоторые клиенты интерфейса могут не ожидать пустого результата. Не изменяя интерфейс, вы позволили реализации создать неожиданный результат.

Когда мы передаем наборы данных с использованием классов Collection, моя команда согласилась, что never вернет null, хотя синтаксически это нормально. Проблема заключалась в том, что классам, которые использовали возвращаемые значения, постоянно приходилось проверять наличие пустот, чтобы предотвратить NPE. Используя приведенный выше код, вы увидите это повсюду:

    if (results != null ){

Потому что теперь есть способ узнать, есть ли у реализации объект или нет. Для конкретного случая вы наверняка знаете, потому что вы знакомы с реализацией. Но как только вы начнете собирать их или они выходят за пределы вашего горизонта кодирования (используется в качестве библиотеки, будущего обслуживания и т. Д.), Проблема с нулевым значением возникнет сама собой.

Далее я попробовал это:

public class SideEffectCommand implements Command<String> {

    @Override
    public String execute(String... args) {
        return "Side Effect";
    }

}

public class NoSideEffectCommand implements Command<Void>{
    @Override
    public Void execute(String... args) {
        return null;
    }   
}
public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();
        Command<?> nsec = new NoSideEffectCommand();

        reportResults(sec.execute("args"));
        reportResults(nsec.execute("args")); //Problem Child
    }   

    public static void reportResults(Object results){
        System.out.println(results.getClass());
    }

    public static void reportResults(Void results){
        System.out.println("Got nothing for you.");
    }
}

Перегрузка не сработала, потому что второй вызов reportResults по-прежнему вызывал версию, ожидающую объект (конечно). Я предполагал изменить его на

public static void reportResults(String results){

Но это проиллюстрировало корень проблемы: ваш клиентский код начинает знать детали реализации. Предполагается, что интерфейсы помогают изолировать зависимости кода, когда это возможно. Добавление их в данный момент кажется плохим дизайном.

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

Это может быть случай неплотных абстракций .

3 голосов
/ 24 июля 2009

Сохраните интерфейс таким, какой он есть: вот почему Void находится в стандартной библиотеке. Пока все, что вызывает команда, ожидает, что null вернется.

И да, null - это единственное значение, которое вы можете вернуть для Void.

Update 2017

За последние несколько лет я избегал Void в качестве типов возврата, кроме случаев, когда речь идет об отражении. Я использовал другой шаблон, который мне кажется более явным , а избегает нулей. То есть у меня есть тип успеха, который я называю Ok, который возвращается для всех команд, таких как OP. Это очень хорошо сработало для моей команды, а также помогло другим командам.

public enum Ok { OK; }

public class SideEffectCommand implements Command<Ok> {
    @Override
    public Ok execute(String... args) {
        ...
        return Ok.OK; // I typically static import Ok.OK;
}
2 голосов
/ 24 июля 2009

Что интересно в вашем примере, так это использование параметризованных типов. Обычно у вас будет

interface Command<T> {
    public T execute(T someObject);
}

В вашем случае в качестве возвращаемого значения у вас будет только T. Тем не менее, использование Void в этом случае является хорошим решением. Возвращаемое значение должно быть null.

1 голос
/ 24 июля 2009

Эта проблема не , что распространено, но и не так уж редко ... Я думаю, что я видел дискуссию об этом некоторое время назад, об Callables, которые ничего не возвращают.

Я согласен с другими авторами, что это хорошее решение, намного лучше, чем использование Object или другого фиктивного заполнителя.

0 голосов
/ 12 августа 2009

Что если бы вместо того, чтобы иметь такой интерфейс, как:

public interface Command<T> {
    T execute(String... args);
}

У вас вместо этого было:

public interface Command<T> {
    void execute(String... args);
    T getResult();
    bool hasResult();
}

Тогда вызывающие абоненты сделали бы:

public void doSomething(Command<?> cmd) {
    cmd.execute(args);
    if(cmd.hasResult()) {
        // ... do something with cmd.getResult() ...
    }
}

Вы могли бытакже создайте интерфейс VoidCommmand, который расширяет Command, если хотите.

Мне кажется, это самое чистое решение.

...