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。
这对我来说似乎是最干净的解决方案。