代码之家  ›  专栏  ›  技术社区  ›  dfa

返回参数为空值

  •  11
  • dfa  · 技术社区  · 16 年前

    我有这个接口:

    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 回复  |  直到 16 年前
        1
  •  4
  •   John Calsbeek    16 年前

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

    // 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; 将强制插入的语句。

        2
  •  10
  •   Tom Hawtin - tackline    16 年前

    我会坚持用 Void 明确地。在没有其他课程参与的情况下,很容易看到正在发生的事情。如果你能覆盖一个 无效 返回 void (和) Integer 具有 int 等等),但这不是优先事项。

        3
  •  4
  •   Aviad Ben Dov    16 年前

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

    更妙的是:在他们的GWT框架中,Google在他们的示例中使用了同样的思想来进行无效的返回回调。( example here )我说:如果谷歌做到了,那肯定至少还可以。:)

        4
  •  3
  •   Kelly S. French    16 年前

    这不是一个常见的问题。您需要解决的问题是对接口的期望。您将非副作用接口的行为与允许副作用的接口结合起来。

    考虑一下:

    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> “意味着接口的某些客户端可能不希望得到无效的结果。在不更改接口的情况下,您允许实现创建意外的结果。

    当我们使用集合类传递数据集时,我的团队同意 从未 即使在语法上很好,也返回空值。问题在于,使用返回值的类必须经常检查是否存在空值,以防止出现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){
    

    但这说明了问题的根源,您的客户机代码开始了解实现的细节。接口应该在可能的时候帮助隔离代码依赖性。在这一点上添加它们似乎是不好的设计。

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

    这可能是 leaky abstractions .

        5
  •  3
  •   Michael Deardeuff    8 年前

    保持接口原样:这就是标准库中void的原因。只要调用命令的对象希望空值返回。

    是的,空值是唯一可以为void返回的值。

    更新2017

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

    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;
    }
    
        6
  •  2
  •   Marcin    16 年前

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

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

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

        7
  •  1
  •   user85421    16 年前

    问题 不是 那个 很普通,但不是很罕见…我想我已经看到一段时间前关于这个的讨论了,关于什么都不返回的可调用文件。

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

        8
  •  -1
  •   Daniel Pryden    16 年前

    如果没有像这样的接口怎么办:

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

    如果愿意,还可以创建一个扩展命令的接口voidcommmand。

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