Void value as return parameter

后端 未结 8 2507
夕颜
夕颜 2021-02-19 18:50

I have this interface:

public interface Command {
    T execute(String... args);
}

it works fine for most uses. But when I try to mode

相关标签:
8条回答
  • 2021-02-19 19:09

    Here's a best-of-multiple-worlds implementation.

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

    With this implementation, clients can talk about a Command if they don't care about the return value, and a ValuedCommand<T> if the need a command that returns a certain value.

    About the only reason not to go with using Void straight up is all the unsightly return null; statements that you will be forced to insert.

    0 讨论(0)
  • 2021-02-19 19:09

    What is interesting in your example is the use of parameterized types. Usually you'd have

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

    In your case you only have T as a return value. Nevertheless using Void in this case is a good solution. The return value should be null.

    0 讨论(0)
  • 2021-02-19 19:12

    This is not a common problem. The issue you need to address is the expectation of your interface. You're combining the behavior of a non-side effect interface with an interface that allows side effects.

    Consider this:

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

    There is nothing wrong with using <Void> as the template type but allowing it to mix with implementations for "Command<T>" means some clients of the interface may not expect a void result. Without changing the interface, you've allowed an implementation to create an unexpected result.

    When we pass around sets of data using the Collection classes, my team agreed to never return null even though it's fine syntactically. The problem was that classes that used the return values would constantly have to check for a void to prevent a NPE. Using the code above, you would see this everywhere:

        if (results != null ){
    

    Because there is now way to know whether the implementation actually has an object or is null. For a specific case, sure you'd know because your familiar with the implementation. But as soon as you start to aggregate them or they pass beyond your coding horizon (used as a library, future maintenance, etc.) the null problem will present itself.

    Next, I tried this:

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

    Overloading didn't work because the second call to reportResults still called the version expecting an Object (of course). I contemplated changing to

    public static void reportResults(String results){
    

    But this illustrated the root of the problem, your client code starts having to know implementation details. Interfaces are supposed to help isolate code dependencies when possible. To add them at this point seems like bad design.

    The bottom line is you need to use a design which makes clear when you expect a command to have a side effect and to think through how you would handle a set of commands, i.e. an array of unknown commands.

    This may be a case of leaky abstractions.

    0 讨论(0)
  • 2021-02-19 19:14

    Keep the interface as it is: this is why Void is in the Standard Library. Just as long as whatever is calling the Command expects nulls to come back out.

    And yes, null is the only value you can return for Void.

    Update 2017

    For the last several years I have avoided Void as return types except where reflection is concerned. I have used a different pattern which I think is more explicit and avoids nulls. That is, I have a success type, which I call Ok which is returned for all commands like the OP's. This has worked very well for my team and has also propagated into other teams' use.

    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;
    }
    
    0 讨论(0)
  • 2021-02-19 19:19

    What if instead of having an interface like:

    public interface Command<T> {
        T execute(String... args);
    }
    

    You instead had:

    public interface Command<T> {
        void execute(String... args);
        T getResult();
        bool hasResult();
    }
    

    Then callers would do:

    public void doSomething(Command<?> cmd) {
        cmd.execute(args);
        if(cmd.hasResult()) {
            // ... do something with cmd.getResult() ...
        }
    }
    

    You could also create an interface VoidCommmand that extends Command, if you like.

    This seems like the cleanest solution to me.

    0 讨论(0)
  • 2021-02-19 19:26

    It looks fine to me. As others said, Void was originally designed for the reflection mechanism, but it is now used in Generics quite often to describe situations such as yours.

    Even better: Google, in their GWT framework, use the same idea in their examples for a void-returning callback (example here). I say: if Google does it, it must be at least OK.. :)

    0 讨论(0)
提交回复
热议问题