12

我有这个界面:

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

8 回答 8

10

我会坚持Void明确使用。在不涉及其他类的情况下很容易看到发生了什么。如果您可以用(和用等)覆盖Void返回,那就太好了,但这不是优先事项。voidIntegerint

于 2009-07-24T13:12:14.347 回答
4

我觉得很好。正如其他人所说,Void最初是为反射机制设计的,但现在在泛型中经常使用它来描述诸如您的情况。

更好的是:Google 在他们的 GWT 框架中,在他们的示例中使用相同的想法来返回 void 回调(示例here)。我说:如果谷歌做到了,那至少应该没问题.. :)

于 2009-08-10T19:25:33.877 回答
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;

于 2009-08-11T22:22:23.110 回答
3

保持接口不变:这就是 Void 在标准库中的原因。只要调用命令的任何东西都期望空值回来。

是的,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;
}
于 2009-07-24T12:02:32.527 回答
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>作为模板类型没有任何问题,但允许它与“命令<T>”的实现混合意味着接口的某些客户端可能不会期望一个 void 结果。在不更改接口的情况下,您已经允许实现创建意外结果。

当我们使用 Collection 类传递数据集时,我的团队同意永远不会返回 null,即使它在语法上很好。问题是使用返回值的类必须经常检查 void 以防止 NPE。使用上面的代码,你会在任何地方看到这个:

    if (results != 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 的第二次调用仍然调用期望对象的版本(当然)。我打算改成

public static void reportResults(String results){

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

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

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

于 2009-08-10T19:14:27.377 回答
2

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

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

在您的情况下,您只有T一个返回值。尽管如此,在这种情况下使用 Void 是一个很好的解决方案。返回值应该是null.

于 2009-07-24T12:06:35.077 回答
1

这个问题并不常见但也不是那么罕见……我想我前段时间看过一个关于这个问题的讨论,关于什么都不返回的 Callables。

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

于 2009-07-24T12:45:47.960 回答
-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 的接口 VoidCommand。

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

于 2009-08-11T22:48:45.677 回答