Значение Void как возвращаемый параметр

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

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);
}

Ответ 1

Здесь реализована реализация "лучший из множества миров".

// 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;, которые вы будете вынуждены вставлять.

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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

Обновление 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;
}

Ответ 5

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

Рассмотрим это:

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>", означает, что некоторые клиенты интерфейса могут не ожидать результата void. Без изменения интерфейса вы позволили реализации создать неожиданный результат.

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

    if (results != null ){

Потому что теперь есть способ узнать, действительно ли реализация имеет объект или имеет значение 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 по-прежнему называется версией, ожидающей Object (конечно). Я рассматривал возможность изменения на

public static void reportResults(String results){

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

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

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

Ответ 6

В вашем примере интереснее использовать параметризованные типы. Обычно у вас будет

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

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

Ответ 7

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

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

Ответ 8

Что, если вместо интерфейса, например:

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, который расширяет команду, если хотите.

Это кажется самым чистым решением для меня.