Значение 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
с помощью Void
(и Integer
с помощью 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, который расширяет команду, если хотите.
Это кажется самым чистым решением для меня.