2
votes

Méthode renvoyant des structures de données alternatives basées sur le résultat de l'exécution

Disons que j'ai une fonction qui regarde un fichier et renvoie deux résultats: reconnu et non reconnu. Quand il renvoie le résultat reconnu, je veux que le résultat contienne également un message, mais lorsqu'il n'est pas reconnu, aucun message n'est nécessaire.

class Result {
}

class Unrecognized extends Result {
}

class Recognized extends Result {
    private String message;
}

Il y a deux façons dont je peux penser pour y parvenir. ..

Ayez la classe Result comme ceci:

class Result {

    private Type type;
    private String message;

    enum Type { 
        RECOGNIZED, UNRECOGNIZED
    }

}

Ou faites-le comme ceci:

public Result checkFile(File file) {
    ...
}

Je suis enclin à utiliser la deuxième méthode, même si je devrais vérifier le résultat en utilisant instanceof et j'ai lu que instanceof code> doit être évité autant que possible, mais cela évite d'avoir un message nul lorsque le résultat n'est pas reconnu. Pour cet exemple, un message nul ne serait pas vraiment un problème, mais que se passe-t-il s'il y a beaucoup plus de données associées à un résultat reconnu? Cela me semble être une pratique pire d'instancier une classe qui pourrait avoir tous les champs nuls.

Quelle est la meilleure pratique pour gérer cette situation? Existe-t-il une méthode ou un modèle standard?


0 commentaires

4 Réponses :


1
votes

Bien sûr, il n'y a pas de conception «correcte» ici - ce sera une question d'opinion dans quelle direction vous allez. Cependant, mon avis est que la tendance moderne en OOD est de minimiser l'utilisation de l'extension et d'utiliser la délégation et la mise en œuvre d'interfaces dans la mesure du possible.

En règle générale, chaque fois que vous pensez utiliser instanceof , reconsidérez votre conception.

Ce serait ma suggestion:

interface Result {
    boolean isRecognised();
    String getMessage();
}

class RecognisedResult implements Result {
    private final String message;

    public boolean isRecognised() {
        return true;
    }

    public String getMessage() {
        return message;
    }
}

class UnrecognisedResult implements Result {
    public boolean isRecognised() {
        return false;
    }

    public String getMessage() {
        throw new UnsupportedOperationException("No message for unrecognised results");
    }
}


12 commentaires

et qu'en est-il lors du retour de ces classes indépendantes, qui implémentent uniquement une interface commune? cela pourrait ne pas convenir à la signature de méthode attendue ... et donc ce serait une valeur de retour de type générique ... peut-être suivie d'une instanceof .


@MartinZeitler désolé je ne comprends pas votre commentaire. la fonction renverrait un Résultat sans avoir besoin de génériques ou instanceof


il n'y a pas de Résultat , il n'y a que RecognisedResult et UnrecognisedResult , qui sont difficiles à pousser via une interface , sauf s'ils 'étendre la même classe de base.


@MartinZeitler désolé, cela n'a aucun sens. Je ne sais pas ce que signifie «pousser à travers une interface». J'ai montré comment ils peuvent implémenter une interface commune qui serait le type de retour de votre méthode.


une interface attend un type de données et un "résultat" est souvent renvoyé via l'implémentation d'une telle interface. pense simplement que cela n'a aucun sens de créer deux types de résultats différents.


Que signifie «une interface attend un type de données»? Je n'ai aucune idée de ce que vous entendez par une interface qui attend quelque chose.


une interface en soi est la définition, a) quelles méthodes doivent être implémentées et b) quels paramètres sont attendus. c'est important pour les callbacks asynchrones ... et deux types de résultat sont une complexité non requise, surtout parce que cela ne facilite rien. et une mauvaise conception d'interface se distribue dans toutes les classes, qui l'implémentent (cette complexité est littéralement multipliée).


Permettez-moi de demander à nouveau. Que signifie «une interface attend un type de données»?


cela signifie qu'avoir deux types de résultats nécessiterait deux méthodes d'interface différentes. la deuxième interface que j'ai proposée a également deux méthodes, mais on s'attend à une exception. les classes peuvent non seulement implémenter des méthodes d'interface, mais faire partie d'une description d'interface (comme le nom «résultat» l'indique déjà).


@MartinZeitler Je suis désolé mais c'est complètement faux. Les deux «types» de résultats implémentent la même interface appelée Résultat . Honnêtement, je soupçonne que nous ne sommes pas sur la même longueur d'onde, donc je ne suis pas sûr qu'il y ait beaucoup d'intérêt à poursuivre cette discussion. Bonne chance.


même si les deux classes implémentent Result , vous pouvez vous retrouver avec instanceof ... il est beaucoup plus courant de renvoyer le résultat et l'erreur dans deux méthodes distinctes, alors il est déjà clair de quoi il s'agit est. c'est peut-être une question de goût, mais c'est aussi pratique.


@MartinZeitler Vous n'avez pas besoin d'une vérification instanceOf , il vous suffit d'appeler la méthode isRecognised sur le type Result . Il semble que vous ne comprenez pas comment fonctionne l'héritage de type.



2
votes

Deux classes peuvent être excessives, car il s'agit d'une seule et même classe d'objets. De plus, un enum avec deux valeurs qui réassemblent simplement true et false n'est pas nécessaire. Un résultat de classe devrait suffire et cela supprimerait également la demande d'une interface commune. Je serais tout pour "pas de complexité au-delà du nécessaire" ...

interface Recognition {
    void OnSuccess(RecognitionResult result);
    void OnFailure(RecognitionException e);
}

alors on peut simplement faire:

interface Recognition {
    void OnComplete(RecognitionResult result);
}

un l'interface pour les rappels asynchrones peut ressembler à ceci:

return new RecognitionResult(true);

ou si vous voulez vraiment optimiser:

class RecognitionResult {

    private String message = "default message";
    private boolean recognized = false;

    public Result() {}

    public Result(boolean value) {
        this.setRecognised(value);
    }

    public boolean setRecognised(boolean value) {
        this.recognized = value;
    }

    public boolean setMessage(@NonNull String value) {
        this.message = value;
    }

    public boolean getRecognised() {
        return this.recognized;
    }

    @Nullable
    public String getMessage() {
        return this.recognized ? this.message : null;
    }
}


4 commentaires

Si vous suivez cette route, vous devez ajouter l'annotation @nullable.


@BakonJarser a ajouté des annotations; mais le @NonNull a plus de sens.


Mais que se passe-t-il si un résultat reconnu a beaucoup plus de données associées qu'un simple message ? Ne serait-ce pas un gaspillage de ressources / mémoire d'instancier un Résultat non reconnu et que tous les champs contiennent une valeur par défaut ou null alors que je n'aurais même pas besoin de les considérer?


@ user7028506 c'est une classe selon vos spécifications. si vous voulez économiser des ressources (à une époque où les téléphones ont 2 à 4 Go de RAM), renvoyez simplement une valeur booléenne ... les chaînes ne doivent pas être codées en dur, en général.



0
votes

vous pouvez voir comment Retrofit met en œuvre votre concept de "reconnu" et de "message" https://square.github.io/retrofit/2. x / retrofit / retrofit2 / Response.html . elle est similaire à votre première méthode.

ce qu'ils ont fait, c'est d'avoir une classe appelée Response, contenant une méthode appelée isSuccessful (), et une méthode appelée body () contenant la charge utile si elle réussit (ou null si elle échoue) .

vous pouvez essayer quelque chose comme ce qui suit

class Result {

    private Type type;
    private String message;

    public bool isSuccessful(){
            return type == RECOGNIZED;
    }

    public String getMessage(){
            return message; //null if unrecognized.
    }

    enum Type { 
        RECOGNIZED, UNRECOGNIZED
    }

}


0 commentaires

0
votes

La manière fonctionnelle de le faire serait d'utiliser un type Either , qui n'est pas fourni avec le JDK, mais est disponible dans vavr . D'après vos commentaires sur ce fil, il semble que vous ne compreniez pas clairement le fonctionnement de l'héritage de type. Dans ce cas, une solution fonctionnelle peut être exagérée, et je vous suggère d’utiliser la solution de @ sprinter.


0 commentaires