我有这个界面:
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);
}
答案 0 :(得分:10)
我会坚持明确地使用Void
。没有其他课程,很容易看到发生了什么。如果您可以使用Void
(以及void
Integer
等)覆盖int
返回,那会很好,但这不是优先事项。
答案 1 :(得分:4)
对我来说很好看。正如其他人所说,Void
最初是为反射机制而设计的,但它现在常用于泛型,以描述你的情况。
更好的是:谷歌在他们的GWT框架中,在他们的例子中使用相同的想法来回归无效的回调(example here)。我说:如果谷歌这样做,它必须至少好......:)
答案 2 :(得分:4)
这是一个最佳的多世界实现。
// 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;
语句。
答案 3 :(得分:3)
保持界面不变:这就是Void在标准库中的原因。只要调用Command的任何内容都需要返回null。
是的,null是你可以为Void返回的唯一值。
在过去的几年里,我避免使用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;
}
答案 4 :(得分:3)
这不是常见问题。您需要解决的问题是您的界面的期望。您将非副作用界面的行为与允许副作用的界面相结合。
考虑一下:
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类传递数据集时,我的团队同意从不返回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){
但这说明了问题的根源,您的客户端代码开始必须知道实现细节。接口应该尽可能帮助隔离代码依赖性。在这一点上添加它们似乎是糟糕的设计。
最重要的是你需要使用一种设计,当你期望一个命令产生副作用时,它会清楚地表明你将如何处理一组命令,即一组未知命令。
这可能是leaky abstractions的情况。
答案 5 :(得分:2)
您的示例中有趣的是使用参数化类型。通常你有
interface Command<T> {
public T execute(T someObject);
}
在您的情况下,您只有T
作为返回值。然而,在这种情况下使用Void是一个很好的解决方案。返回值应为null
。
答案 6 :(得分:1)
那个问题不是那个常见的,但这两种情况都不常见......我想我前段时间已经看到过关于Callables的讨论
我同意其他海报,这是一个很好的解决方案,比使用Object或其他虚拟占位符要好得多。
答案 7 :(得分:-1)
如果没有像:
这样的界面,该怎么办?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() ...
}
}
如果愿意,您还可以创建一个扩展Command的接口VoidCommmand。
这对我来说似乎是最干净的解决方案。