web-dev-qa-db-fra.com

Java - Comment créer uniquement un objet avec des attributs valides?

Je fais un cours de base en Java et je suis tombé sur un problème: comment créer un objet uniquement si j'ai transmis des paramètres valides au constructeur?

Devrais-je créer une classe alternative et appeler le constructeur à partir de là une fois la validation réalisée?

Ou devrais-je/pourrais-je utiliser une méthode statique dans la classe pour la validation?

Quelle est la meilleure pratique dans ce cas?

17
Tiago Sirious

La pratique standard consiste à valider les arguments dans le constructeur. Par exemple:

class Range {
  private final int low, high;
  Range(int low, int high) {
    if (low > high) throw new IllegalArgumentException("low can't be greater than high");
    this.low = low;
    this.high = high;
  }
}

Note latérale: pour vérifier que les arguments ne sont pas nuls, ce qui est assez courant, vous pouvez utiliser:

import static Java.util.Objects.requireNonNull;

Constructor(Object o) {
  this.o = requireNonNull(o); //throws a NullPointerException if 'o' is null
}

METTRE À JOUR

Pour répondre à votre commentaire spécifique sur le numéro de sécurité sociale. Une solution serait d’ajouter une méthode à la classe:

//constructor
public YourClass(String ssn) {
  if (!isValidSSN(ssn)) throw new IllegalArgumentException("not a valid SSN: " + ssn);
  this.ssn = ssn;
}

public static boolean isValidSSN(String ssn) {
  //do some validation logic
}

Le code d'appel pourrait alors ressembler à:

String ssn = getSsnFromUser();
while(!YourClass.isValidSSN(ssn)) {
  showErrorMessage("Not a valid ssn: " + ssn);
  ssn = getSsnFromUser();
}
//at this point, the SSN is valid:
YourClass yc = new YourClass(ssn);

Avec cette conception, vous avez réalisé deux choses:

  • vous validez la saisie de l'utilisateur avant de l'utiliser (ce que vous devez toujours faire - les utilisateurs sont très doués pour les fautes de frappe)
  • vous vous êtes assuré que si YourClass est mal utilisé, une exception est levée et cela vous aidera à détecter les bogues

Vous pourriez aller plus loin en créant une classe SSN qui contient le SSN et encapsule la logique de validation. YourClass accepterait alors un objet SSN comme argument, qui est toujours un SSN valide par construction.

28
assylias

Je voudrais juste jeter un IllegalArgumentException dans le constructeur lui-même:

public class MyClass {
    private int i;

    public MyClass (int i) {
        // example validation:
        if (i < 0) {
            throw new IllegalArgumentException ("i mustn't be negatve!");
        }
        this.i = i;
}
9
Mureinik

Un truisme bien connu en programmation est le suivant: «Ne pas utiliser les exceptions pour le contrôle de flux». Votre code doit connaître les restrictions et se prémunir contre elles avant d'appeler le constructeur plutôt que de gérer les erreurs. Des exceptions existent pour des circonstances prévisionnelles, en particulier des circonstances impossibles à prévoir ou à protéger (par exemple, un flux IO peut devenir invalide pendant l'écriture, bien qu'il soit OK lors d'un contrôle précédent).

Bien que vous puissiez créer des exceptions dans votre constructeur, cela n’est pas toujours idéal. Si vous écrivez des objets publics que vous comptez utiliser/réutiliser par d’autres personnes, les exceptions sont la seule option réelle pour les constructeurs publics. Toutefois, ces limitations et leur résultat (par exemple, quelle exception sera levée) doivent être clairement documentés dans le javadoc classe.

Pour les classes internes, les assertions sont plus appropriées. Comme le précise Oracle: "Les assertions ... doivent être utilisées pour vérifier les cas qui ne devraient jamais se produire, vérifier les hypothèses sur les structures de données ou appliquer des contraintes aux arguments des méthodes privées." — Utilisation des assertions dans la technologie Java . Vous devriez probablement toujours documenter vos attentes pour le cours, mais votre application devrait faire des vérifications au préalable en interne plutôt que de compter sur des exceptions.

Les méthodes de fabrique statique peuvent aider un peu, leurs avantages se développant un peu par une autre question: Comment utiliser les "méthodes de fabrique statique" au lieu de constructeurs . Cependant, ils ne donnent pas d’options de validation solides sans, encore une fois, s’appuyer sur Exceptions lorsque des éléments ne sont pas valides (ou que renvoyer null, ce qui est moins informatif).

Votre solution idéale est le modèle de générateur . Cela permet non seulement une plus grande souplesse dans la gestion de vos arguments, mais vous pouvez également les valider individuellement ou disposer d'une méthode de validation permettant d'évaluer simultanément tous les champs. Un constructeur peut et doit être utilisé pour masquer le constructeur réel de l'objet, y accéder uniquement et empêcher la transmission de valeurs non souhaitées, tandis que les assertions peuvent protéger contre "le constructeur ne doit jamais soumettre ces valeurs".

7
Danikov

Les constructeurs peuvent générer des exceptions (voir Les constructeurs peuvent-ils générer des exceptions en Java? ) afin que votre constructeur lève une exception si des valeurs non valides sont transmises. Vous pouvez également rendre votre constructeur privé et utiliser la méthode statique pour créer votre objet, qui effectue les vérifications. Cela pourrait être plus propre.

4
JP Moresmau

Pour vous assurer que des paramètres valides sont transmis au constructeur, vous pouvez créer la classe parente avec des constructeurs acceptant uniquement les paramètres requis, puis créer une sous-classe utilisée par vos utilisateurs finaux. Si vous obligez votre utilisateur à appeler super () et à lui transmettre les paramètres requis, il doit au moins transmettre les bons objets de données. En ce qui concerne les valeurs valides pour ces paramètres, c'est à vous de décider si vous souhaitez inclure la validation dans le constructeur de la classe parent et déclencher des exceptions d'exécution ou autre chose.

Voici un exemple de la superclasse/sous-classe. Appelons la superlasse SomeShape et la sous-classe Triangle. Pour tout objet SomeShape, vous allez forcer l'utilisateur à fournir un nombre de côtés et une longueur de côté. C'est ainsi...

public class SomeShape {
    private int numSides;
    private int sideLength;

    public SomeShape(int mNumSides, int mSideLength) {
        numSides = mNumSides;
        sideLength = mSideLength;
    }
}

public class Triangle extends SomeShape {
    private int height;

    public Triangle(int mNumSides, int mSideLength, int mHeight) {
        super(mNumSides, mSideLength);
        height = mHeight;
    }   
}

En plus de coder en dur une série de logiques et d'exceptions jetées dans votre constructeur, il s'agit d'une méthode relativement simple pour appliquer les paramètres requis pour créer l'objet.

1
Chamatake-san

Si vous ne voulez pas lancer d'exception du constructeur, vous pouvez rendre le constructeur privé et créer une méthode statique qui renvoie une nouvelle instance de l'objet, ou null si les arguments ne sont pas valides. L'appelant de cette méthode devrait toutefois vérifier si le résultat est nul ou non.

Exemple:

public class Foo {
  private Foo(int arg1, Bar arg2) {
    // guaranteed to be valid.
  }

  public static Foo construct(int arg1, Bar arg2) {
    // perform validation
    if (arg1 < 0 || arg2 == null) {
      return null;
    } else {
      return new Foo(arg1, arg2);
    }
  }
}

Utilisation

Foo object = Foo.construct(1, new Bar());
if (object == null) {
  // handle error here.
}
0
MusicMaster