Void值作为返回参数

我有这个界面:

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

它适用于大多数用途。 但是当我尝试模拟一个只有副作用的命令(例如没有返回值)时,我很想写:

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

这是个常见的问题吗? 是否有使用不使用返回值来建模Commands最佳实践?

我试过这个适配器,但我认为这不是最佳的,原因如下:

 public abstract class VoidCommand implements Command { @Override public Void execute(String... args) { execute2(args); return null; } public abstract void execute2(String... args); } 

这是一个最好的多世界实现。

 // 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 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 extends Command { public T evaluate(String[] arguments); } // Optional, but useful if most of your commands are ValuedCommands. public abstract class AbstractCommand implements ValuedCommand { 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  void execute(ValuedCommand 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 { private final Command cmd; public VoidCommand(Command actual) { cmd = actual; } public Void evaluate(String[] arguments) { cmd.execute(arguments); return null; } } } 

通过此实现,客户端可以在不关心返回值时讨论Command如果需要返回特定值的命令,则可以使用ValuedCommand

关于不使用Void直接使用的唯一原因是所有难看的return null; 您将被迫插入的陈述。

我会坚持使用Void明确。 没有其他课程,很容易看到发生了什么。 如果你可以使用void覆盖Void返回(以及使用int等的Integer ),那会很好,但这不是优先事项。

它看起来很好。 正如其他人所说, Void最初是为reflection机制而设计的,但它现在经常用于generics,以描述你的情况。

更好的是:谷歌在他们的GWT框架中,在他们的例子中使用相同的想法来进行返回虚假的回调( 例如这里 )。 我说:如果谷歌这样做,它必须至少好.. 🙂

这不是常见问题。 您需要解决的问题是您的界面的期望。 您将非副作用界面的行为与允许副作用的界面相结合。

考虑一下:

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

使用作为模板类型但允许它与“Command ”的实现混合是没有错的,这意味着接口的某些客户端可能不会期望无效结果。 在不更改接口的情况下,您已允许实现创建意外结果。

当我们使用Collection类传递数据集时,我的团队同意永远不会返回null,即使它在语法上很好。 问题是使用返回值的类将不断检查空白以防止NPE。 使用上面的代码,你会看到无处不在:

  if (results != null ){ 

因为现在有办法知道实现是否实际上有一个对象或是否为null。 对于特定情况,确定您知道因为您熟悉实施。 但是一旦你开始聚合它们或者它们超出你的编码范围(用作库,未来的维护等),就会出现空问题。

接下来,我尝试了这个:

 public class SideEffectCommand implements Command { @Override public String execute(String... args) { return "Side Effect"; } } public class NoSideEffectCommand implements Command{ @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){ 

但这说明了问题的根源,您的客户端代码开始必须知道实现细节。 接口应该尽可能帮助隔离代码依赖性。 在这一点上添加它们似乎是糟糕的设计。

最重要的是你需要使用一种设计,当你期望一个命令产生副作用时,它会清楚地表明你将如何处理一组命令,即一组未知命令。

这可能是泄漏抽象的情况。

保持界面不变:这就是Void在标准库中的原因。 只要调用Command的任何东西都要求null返回。

是的,null是你可以为Void返回的唯一值。

2017年更新

在过去的几年里,我避免使用Void作为返回类型,除非涉及reflection。 我使用了一种不同的模式,我认为它更明确避免了空值。 也就是说,我有一个成功类型,我称之为Ok ,它会返回所有命令,如OP。 这对我的团队来说非常有效,并且还传播到其他团队的使用中。

 public enum Ok { OK; } public class SideEffectCommand implements Command { @Override public Ok execute(String... args) { ... return Ok.OK; // I typically static import Ok.OK; } 

您的示例中有趣的是使用参数化类型。 通常你会有

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

在您的情况下,您只有T作为返回值。 然而,在这种情况下使用Void是一个很好的解决方案。 返回值应为null

这个问题并不常见,但也不是那么罕见……我想我前段时间已经看到过关于Callables什么也没有回复的讨论。

我同意其他海报,这是一个很好的解决方案,比使用Object或其他虚拟占位符要好得多。

如果没有像以下那样的界面怎么办?

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

你改为:

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

然后来电者会这样做:

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

如果愿意,您还可以创建一个扩展Command的接口VoidCommmand。

这对我来说似乎是最干净的解决方案。