Void值作为返回参数

时间:2009-07-24 11:41:00

标签: java command adapter

我有这个界面:

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

8 个答案:

答案 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返回的唯一值。

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

答案 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。

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