Meilleur moyen de gérer plusieurs constructeurs en Java

Je me demande quel est le meilleur moyen (c’est-à-dire le plus propre / le plus sûr / le plus efficace) de gérer plusieurs constructeurs en Java? Surtout quand dans un ou plusieurs constructeurs tous les champs ne sont pas spécifiés:

public class Book { private Ssortingng title; private Ssortingng isbn; public Book() { //nothing specified! } public Book(Ssortingng title) { //only title! } ... } 

Que dois-je faire lorsque les champs ne sont pas spécifiés? J’ai jusqu’ici utilisé des valeurs par défaut dans la classe pour qu’un champ ne soit jamais nul, mais est-ce une “bonne” façon de faire les choses?

Une réponse légèrement simplifiée:

 public class Book { private final Ssortingng title; public Book(Ssortingng title) { this.title = title; } public Book() { this("Default Title"); } ... } 

Envisagez d’utiliser le modèle de générateur. Cela vous permet de définir les valeurs par défaut de vos parameters et de les initialiser de manière claire et concise. Par exemple:

 Book b = new Book.Builder("Catcher in the Rye").Isbn("12345") .Weight("5 pounds").build(); 

Edit: Cela supprime également le besoin de plusieurs constructeurs avec des signatures différentes et est beaucoup plus lisible.

Vous devez spécifier quels sont les invariants de classe, c’est-à-dire les propriétés qui seront toujours vraies pour une instance de la classe (par exemple, le titre d’un livre ne sera jamais nul ou la taille d’un chien sera toujours> 0).

Ces invariants doivent être établis pendant la construction et conservés tout au long de la vie de l’object, ce qui signifie que les méthodes ne doivent pas casser les invariants. Les constructeurs peuvent définir ces invariants soit en ayant des arguments obligatoires, soit en définissant des valeurs par défaut:

 class Book { private Ssortingng title; // not nullable private Ssortingng isbn; // nullable // Here we provide a default value, but we could also skip the // parameterless constructor entirely, to force users of the class to // provide a title public Book() { this("Untitled"); } public Book(Ssortingng title) throws IllegalArgumentException { if (title == null) throw new IllegalArgumentException("Book title can't be null"); this.title = title; // leave isbn without value } // Constructor with title and isbn } 

Cependant, le choix de ces invariants dépend fortement de la classe que vous écrivez, de la manière dont vous l’utilisez, etc., il n’ya donc pas de réponse définitive à votre question.

Vous devez toujours construire un object valide et légitime; et si vous ne pouvez pas utiliser de parameters constructeur, vous devez utiliser un object générateur pour en créer un, libérant uniquement l’object du générateur lorsque l’object est terminé.

Sur la question de l’utilisation par les constructeurs: j’essaie toujours d’avoir un constructeur de base auquel tous les autres se réfèrent, en enchaînant avec des parameters “omis” au constructeur logique suivant et en terminant au constructeur de base. Alors:

 class SomeClass { SomeClass() { this("DefaultA"); } SomeClass(Ssortingng a) { this(a,"DefaultB"); } SomeClass(Ssortingng a, Ssortingng b) { myA=a; myB=b; } ... } 

Si ce n’est pas possible, j’essaie d’avoir une méthode privée init () à laquelle tous les constructeurs ont recours.

Et gardez le nombre de constructeurs et les parameters petits – un maximum de 5 de chaque comme un guide.

Quelques astuces générales de constructeur:

  • Essayez de concentrer toute l’initialisation dans un seul constructeur et appelez-le à partir des autres constructeurs
    • Cela fonctionne bien si plusieurs constructeurs existent pour simuler les parameters par défaut
  • Ne jamais appeler une méthode non finale d’un constructeur
    • Les méthodes privées sont finales par définition
    • Le polymorphism peut vous tuer ici; vous pouvez finir par appeler une implémentation de sous-classe avant l’initialisation de la sous-classe
    • Si vous avez besoin de méthodes “d’assistance”, assurez-vous de les rendre privées ou définitives.
  • Soyez explicite dans vos appels à super ()
    • Vous seriez surpris de voir combien de programmeurs Java ne réalisent pas que super () est appelé même si vous ne l’écrivez pas explicitement (en supposant que vous ne l’appeliez pas (…))
  • Connaître l’ordre des règles d’initialisation pour les constructeurs. C’est fondamentalement:

    1. ceci (…) s’il est présent (déplacez-vous simplement vers un autre constructeur)
    2. appelez super (…) [si pas explicite, appelez super () implicitement]
    3. (construire une super-classe en utilisant ces règles de manière récursive)
    4. initialiser les champs via leurs déclarations
    5. exécuter le corps du constructeur actuel
    6. retourner aux constructeurs précédents (si vous aviez rencontré cet appel (…))

Le stream global finit par être:

  • remonter la hiérarchie de la superclasse vers Object
  • bien que non fait
    • champs init
    • exécuter des corps de constructeurs
    • descendre à la sous-classe

Pour un bel exemple du mal, essayez de déterminer ce que les éléments suivants vont imprimer, puis lancez-le

 package com.javadude.sample; /** THIS IS REALLY EVIL CODE! BEWARE!!! */ class A { private int x = 10; public A() { init(); } protected void init() { x = 20; } public int getX() { return x; } } class B extends A { private int y = 42; protected void init() { y = getX(); } public int getY() { return y; } } public class Test { public static void main(Ssortingng[] args) { B b = new B(); System.out.println("x=" + b.getX()); System.out.println("y=" + b.getY()); } } 

J’appendai des commentaires décrivant les raisons pour lesquelles cela fonctionne comme ça … Certains peuvent être évidents; certains ne sont pas …

Une autre considération, si un champ est requirejs ou a une scope limitée, effectuez la vérification dans le constructeur:

 public Book(Ssortingng title) { if (title==null) throw new IllegalArgumentException("title can't be null"); this.title = title; } 

Il pourrait être intéressant d’envisager l’utilisation d’une méthode de fabrique statique au lieu du constructeur.

Je dis plutôt , mais vous ne pouvez évidemment pas remplacer le constructeur. Ce que vous pouvez faire, cependant, est de cacher le constructeur derrière une méthode de fabrique statique. De cette façon, nous publions la méthode de la fabrique statique dans le cadre de l’API de la classe, mais en même temps, nous masquons le constructeur en le rendant privé ou privé.

C’est une solution relativement simple, surtout en comparaison avec le modèle Builder (vu dans Effective Java 2nd Edition de Joshua Bloch – méfiez-vous, les Pattern Designs de Gang of Four définissent un modèle de conception complètement différent avec le même nom). implique la création d’une classe nestede, d’un object générateur, etc.

Cette approche ajoute une couche supplémentaire d’abstraction entre vous et votre client, en renforçant l’encapsulation et en facilitant les changements. Il vous donne également le contrôle d’instance – puisque les objects sont instanciés à l’intérieur de la classe, vous et le client décidez quand et comment ces objects sont créés.

Enfin, cela facilite les tests – en fournissant un constructeur muet, qui assigne simplement les valeurs aux champs, sans effectuer aucune logique ou validation, cela vous permet d’introduire un état invalide dans votre système pour tester son comportement et réagir à cela. Vous ne pourrez pas faire cela si vous validez des données dans le constructeur.

Vous pouvez lire beaucoup plus à ce sujet dans (mentionné précédemment) Effective Java 2nd Edition de Joshua Bloch – c’est un outil important dans toutes les boîtes à outils du développeur et ce n’est pas étonnant qu’il soit le sujet du premier chapitre du livre. 😉

Suivant votre exemple:

 public class Book { private static final Ssortingng DEFAULT_TITLE = "The Importance of Being Ernest"; private final Ssortingng title; private final Ssortingng isbn; private Book(Ssortingng title, Ssortingng isbn) { this.title = title; this.isbn = isbn; } public static Book createBook(Ssortingng title, Ssortingng isbn) { return new Book(title, isbn); } public static Book createBookWithDefaultTitle(Ssortingng isbn) { return new Book(DEFAULT_TITLE, isbn); } ... 

}

Quelle que soit la manière que vous choisissiez, il est conseillé d’avoir un constructeur principal , qui atsortingbue simplement toutes les valeurs à l’aveugle, même s’il est utilisé par un autre constructeur.

Je ferais ce qui suit:

 livre de classe publique
 {
     titre de cordes final privé;
     finale privée Ssortingng isbn;

     Livre public (final Ssortingng t, final Ssortingng i)
     {
         if (t == null)
         {
             lancer une nouvelle exception IllegalArgumentException ("t ne peut pas être nul");
         }

         if (i == null)
         {
             lancer une nouvelle exception IllegalArgumentException ("je ne peux pas être nul");
         }

         titre = t;
         isbn = i;
     }
 }

Je fais l’hypothèse ici que:

1) le titre ne changera jamais (le titre est donc définitif) 2) le isbn ne changera jamais (donc isbn est final) 3) qu’il n’est pas valable d’avoir un livre sans titre ni isbn.

Considérons une classe d’élèves:

 étudiant de classe publique
 {
     identifiant final de StudentID privé;
     private Ssortingng firstName;
     private Ssortingng lastName;

     Etudiant public (StudentID final i,
                    Corde finale en premier,
                    Chaîne finale en dernier)
     {
         if (i == null)
         {
             lancer une nouvelle exception IllegalArgumentException ("je ne peux pas être nul"); 
         }

         if (first == null)
         {
             lancer une nouvelle exception IllegalArgumentException ("first ne peut pas être nul"); 
         }

         if (last == null)
         {
             lancer une nouvelle exception IllegalArgumentException ("last ne peut pas être nul"); 
         }

         id = i;
         firstName = first;
         lastName = last;
     }
 }

Là, un étudiant doit être créé avec un identifiant, un prénom et un nom de famille. L’identifiant de l’élève ne peut jamais changer, mais le nom et le prénom d’une personne peuvent changer (se marier, changer de nom en raison de la perte d’un pari, etc.).

Lorsque vous décidez quels constructeurs vous devez avoir, vous devez vraiment réfléchir à ce qui est logique. Souvent, les gens ajoutent des méthodes set / get car on leur enseigne – mais très souvent, c’est une mauvaise idée.

Les classes immuables sont bien mieux d’avoir (c’est-à-dire des classes avec des variables finales) que des classes mutables. Ce livre: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1 (Java efficace) a une bonne discussion sur l’immuabilité. Regardez les articles 12 et 13.

Plusieurs personnes ont recommandé d’append une vérification NULL. Parfois, c’est la bonne chose à faire, mais pas toujours. Découvrez cet excellent article montrant pourquoi vous l’ignoreriez.

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/