Question

J'ai cette interface:

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

il fonctionne très bien pour la plupart des utilisations. Mais lorsque je tente de modéliser une commande qui n'ont que des effets secondaires (par exemple sans valeur de retour) Je suis tenté d'écrire:

public class SideEffectCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
        return null; // null is fine?
    }
} 

Est-ce un problème commun? Sont les meilleures pratiques là pour modéliser Commands avec et sans valeur de retour?

Je l'ai essayé avec cet adaptateur, mais je pense que ce n'est pas optimale pour plusieurs raisons:

public abstract class VoidCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
       execute2(args);
       return null;
    }

    public abstract void execute2(String... args);
}
Était-ce utile?

La solution

Voici un best-of-multiples mondes mise en œuvre.

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

Avec cette implémentation, les clients peuvent parler d'un Command si elles ne se soucient pas de la valeur de retour, et un ValuedCommand<T> si la nécessité d'une commande qui retourne une certaine valeur.

A propos de la seule raison de ne pas aller à l'utilisation Void est directement toutes les déclarations de return null; disgracieux que vous serez forcé d'insérer.

Autres conseils

Je tiendrais à utiliser Void explicitement. Il est facile de voir ce qui se passe sans une autre classe impliquée. Ce serait bien si vous pouviez passer outre un retour de Void avec void (et Integer avec int, etc.), mais ce n'est pas une priorité.

Il semble bien pour moi. Comme d'autres ont dit, Void a été conçu à l'origine pour le mécanisme de réflexion, mais il est maintenant utilisé dans Generics assez souvent pour décrire des situations telles que la vôtre.

Mieux encore: Google, dans leur cadre GWT, utilisez la même idée dans leurs exemples pour un rappel vide-retour ( exemple ). Je dis: si Google il le fait, il doit être au moins OK ..:)

Ce n'est pas un problème commun. La question que vous devez répondre est l'attente de votre interface. Vous combinez le comportement d'une interface effet non côté avec une interface qui permet des effets secondaires.

Considérez ceci:

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

Il n'y a rien de mal à utiliser <Void> comme type de modèle, mais ce qui lui permet de se mélanger avec les mises en œuvre pour « Command<T> » signifie que certains clients de l'interface ne peut pas attendre à un résultat vide. Sans changer l'interface, vous avez permis une mise en œuvre pour créer un résultat inattendu.

Quand on passe autour de séries de données utilisant les classes de collection, mon équipe a accepté de jamais null retour même si elle est bien syntaxiquement. Le problème était que les classes qui ont utilisé les valeurs de retour devraient constamment vérifier un vide pour éviter un NPE. En utilisant le code ci-dessus, vous verrez cela partout:

    if (results != null ){

Parce qu'il ya maintenant moyen de savoir si la mise en œuvre a en fait un objet ou est nul. Pour un cas précis, sûr que vous savez que votre familier avec la mise en œuvre. Mais dès que vous commencez à les regrouper ou ils passent au-delà de votre horizon de codage (utilisé comme une bibliothèque, l'entretien futur, etc.), le problème se présentera nulle.

Ensuite, j'ai essayé ceci:

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

ne fonctionne pas Surcharge parce que le deuxième appel à reportResults encore appelé la version attend un objet (bien sûr). Je contemplais le passage à

public static void reportResults(String results){

Mais cela illustre la racine du problème, votre code client commence à avoir à connaître les détails de mise en œuvre. Les interfaces sont censés aider les dépendances de code isoler lorsque cela est possible. Pour les ajouter à ce point semble être une mauvaise conception.

L'essentiel est que vous devez utiliser un design qui fait clairement quand vous attendez une commande d'avoir un effet secondaire et de réfléchir à la façon dont vous gérer un ensemble de commandes, à savoir un tableau de commandes inconnues.

Cela peut être un cas de abstractions qui fuient .

Gardez l'interface comme il est: voilà pourquoi Void est dans la bibliothèque standard. Aussi longtemps que ce qui appelle le commandement attend nulls de revenir sur.

Et oui, null est la seule valeur que vous pouvez revenir pour Void.

Mise à jour 2017

Depuis plusieurs années, j'ai évité Void que les types de retour, sauf si la réflexion est concerné. Je l'ai utilisé un modèle différent qui je pense est plus explicite et évite nulls. C'est, j'ai un type de succès, que j'appelle Ok qui est retourné pour toutes les commandes comme celles de l'OP. Cela a très bien fonctionné pour mon équipe et a également propagée dans l'utilisation des autres équipes.

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

Ce qui est intéressant dans votre exemple est l'utilisation de types paramétrés. Habituellement, vous auriez

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

Dans votre cas, vous n'avez T en tant que valeur de retour. Néanmoins l'utilisation Void dans ce cas est une bonne solution. La valeur de retour doit être null.

Ce problème est pas commun, mais ni que rare ... Je pense que je l'ai vu une discussion à ce sujet il y a quelque temps, à propos de appelables qui retournent rien .

Je suis d'accord avec les autres affiches que c'est une bonne solution, beaucoup mieux que d'utiliser un objet ou d'un autre espace réservé factice.

Et si au lieu d'avoir une interface comme:

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

Vous aviez la place:

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

Alors les appelants feraient:

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

Vous pouvez également créer un VoidCommmand d'interface qui étend de commande, si vous le souhaitez.

Cela semble à moi la solution la plus propre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top