Question

Motivation

Récemment, j'ai recherché un moyen d'initialiser un objet complexe sans transmettre beaucoup de paramètres au constructeur. J'ai essayé avec le modèle de construction, mais je n'aime pas le fait que je ne puisse pas vérifier au moment de la compilation si je définis vraiment toutes les valeurs requises.

Modèle de générateur traditionnel

Lorsque j'utilise le modèle de générateur pour créer mon objet Complex , la création correspond davantage à "typesafe", car il est plus facile de voir à quoi sert un argument:

new ComplexBuilder()
        .setFirst( "first" )
        .setSecond( "second" )
        .setThird( "third" )
        ...
        .build();

Mais maintenant, le problème est que je peux facilement rater un paramètre important. Je peux le vérifier dans la méthode build () , mais ce n’est qu’au moment de l’exécution. Au moment de la compilation, rien ne me prévient si quelque chose me manque.

Modèle de générateur amélioré

À présent, mon idée était de créer un constructeur, qui "rappelle". moi si je manquais un paramètre nécessaire. Mon premier essai ressemble à ceci:

public class Complex {
    private String m_first;
    private String m_second;
    private String m_third;

    private Complex() {}

    public static class ComplexBuilder {
        private Complex m_complex;

        public ComplexBuilder() {
            m_complex = new Complex();
        }

        public Builder2 setFirst( String first ) {
            m_complex.m_first = first;
            return new Builder2();
        }

        public class Builder2 {
            private Builder2() {}
            Builder3 setSecond( String second ) {
                m_complex.m_second = second;
                return new Builder3();
            }
        }

        public class Builder3 {
            private Builder3() {}
            Builder4 setThird( String third ) {
                m_complex.m_third = third;
                return new Builder4();
            }
        }

        public class Builder4 {
            private Builder4() {}
            Complex build() {
                return m_complex;
            }
        }
    }
}

Comme vous pouvez le constater, chaque opérateur de la classe de générateur renvoie une classe de générateur interne différente. Chaque classe de générateur interne fournit exactement une méthode de définition et la dernière, une méthode build () uniquement.

Maintenant, la construction d'un objet ressemble à ceci:

new ComplexBuilder()
    .setFirst( "first" )
    .setSecond( "second" )
    .setThird( "third" )
    .build();

... mais il n'y a aucun moyen d'oublier un paramètre nécessaire. Le compilateur ne l’acceptera pas.

Paramètres facultatifs

Si j’avais des paramètres facultatifs, j’utiliserais la dernière classe de constructeur interne Builder4 pour les définir comme un "traditionnel". constructeur le fait, en se retournant.

Questions

  • Est-ce un modèle bien connu? At-il un nom spécial?
  • Voyez-vous des pièges?
  • Avez-vous des idées pour améliorer la mise en œuvre, c'est-à-dire réduire le nombre de lignes de code?
Était-ce utile?

La solution

Non, ce n'est pas nouveau. En réalité, vous créez une sorte de DSL en développant le générateur de normes. modèle permettant de prendre en charge les branches, ce qui est entre autres un excellent moyen de s’assurer que le générateur ne génère pas un ensemble de paramètres en conflit avec l’objet réel.

Personnellement, j’estime que c’est une excellente extension du modèle de générateur et que vous pouvez faire toutes sortes de choses intéressantes. Par exemple, au travail, nous avons des générateurs DSL pour certains de nos tests d’intégrité des données, ce qui nous permet de faire des choses comme assertMachine (). usesElectricity (). and (). makesGrindingNoises (). whenTurnedOn (); . OK, peut-être pas le meilleur exemple possible mais je pense que vous avez compris.

Autres conseils

Le modèle de construction traditionnel gère déjà cela: il suffit de prendre les paramètres obligatoires dans le constructeur. Bien entendu, rien n'empêche un appelant de transmettre la valeur null, mais votre méthode non plus.

Le gros problème que je vois avec votre méthode est que vous avez soit une explosion combinatoire de classes avec le nombre de paramètres obligatoires, soit vous obligez l'utilisateur à définir les paramètres dans une séquence particulière, ce qui est gênant.

De plus, cela représente beaucoup de travail supplémentaire.

public class Complex {
    private final String first;
    private final String second;
    private final String third;

    public static class False {}
    public static class True {}

    public static class Builder<Has1,Has2,Has3> {
        private String first;
        private String second;
        private String third;

        private Builder() {}

        public static Builder<False,False,False> create() {
            return new Builder<>();
        }

        public Builder<True,Has2,Has3> setFirst(String first) {
            this.first = first;
            return (Builder<True,Has2,Has3>)this;
        }

        public Builder<Has1,True,Has3> setSecond(String second) {
            this.second = second;
            return (Builder<Has1,True,Has3>)this;
        }

        public Builder<Has1,Has2,True> setThird(String third) {
            this.third = third;
            return (Builder<Has1,Has2,True>)this;
        }
    }

    public Complex(Builder<True,True,True> builder) {
        first = builder.first;
        second = builder.second;
        third = builder.third;
    }

    public static void test() {
        // Compile Error!
        Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2"));

        // Compile Error!
        Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3"));

        // Works!, all params supplied.
        Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3"));
    }
}

Pourquoi ne mettez-vous pas "besoin" " paramètres dans le constructeur de constructeurs?

public class Complex
{
....
  public static class ComplexBuilder
  {
     // Required parameters
     private final int required;

     // Optional parameters
     private int optional = 0;

     public ComplexBuilder( int required )
     {
        this.required = required;
     } 

     public Builder setOptional(int optional)
     {
        this.optional = optional;
     }
  }
...
}

Ce modèle est décrit dans Java effectif .

Au lieu d’utiliser plusieurs classes, j’utiliserais une seule classe et plusieurs interfaces. Il applique votre syntaxe sans nécessiter autant de frappe. Il vous permet également de voir tous les codes associés proches les uns des autres, ce qui permet de comprendre plus facilement ce qui se passe avec votre code.

IMHO, cela semble gonflé. Si vous devez disposer de tous les paramètres, transmettez-les au constructeur.

J'ai vu / utilisé ceci:

new ComplexBuilder(requiredvarA, requiedVarB).optional(foo).optional(bar).build();

Ensuite, transmettez-les à votre objet qui les requiert.

Le modèle de générateur est généralement utilisé lorsque vous avez beaucoup de paramètres facultatifs. Si vous constatez que vous avez besoin de nombreux paramètres obligatoires, examinez d'abord ces options:

  • Votre classe en fait peut-être trop. Vérifiez à nouveau qu'il n'enfreint pas le principe de responsabilité unique . Demandez-vous pourquoi vous avez besoin d'une classe avec autant de variables d'instance requises.
  • Votre constructeur peut être en faire trop . Le travail d'un constructeur est de construire. (Ils n'ont pas été très créatifs quand ils l'ont nommé; D) Tout comme les classes, les méthodes ont un principe de responsabilité unique. Si votre constructeur fait plus que simplement assigner des champs, vous avez besoin d’une bonne raison pour le justifier. Vous constaterez peut-être que vous avez besoin d'une méthode d'usine plutôt que d'un générateur.
  • Vos paramètres peuvent être faire trop peu . Demandez-vous si vos paramètres peuvent être regroupés dans une petite structure (ou un objet de type structure dans le cas de Java). N'ayez pas peur de faire de petites classes. Si vous avez besoin de créer une structure ou une petite classe, n'oubliez pas de to refactor sur fonctionnalité qui appartient à la structure plutôt qu'à votre classe plus importante.

Pour plus d'informations sur quand utiliser le modèle de constructeur et ses avantages , vous devriez consulter mon message pour une autre question similaire ici

Question 1: En ce qui concerne le nom du motif, j'aime bien le nom "Step Builder":

Question 2/3: En ce qui concerne les pièges et les recommandations, cela semble trop compliqué dans la plupart des situations.

  • Vous appliquez une séquence à votre utilisation de votre générateur, ce qui est inhabituel dans mon expérience. Je pouvais voir à quel point cela serait important dans certains cas, mais je n'en ai jamais eu besoin. Par exemple, je ne vois pas la nécessité de forcer une séquence ici:

    Person.builder (). firstName ("John"). lastName ("Doe"). build () Person.builder (). lastName ("Doe"). firstName ("John"). build ()

  • Cependant, le constructeur a souvent eu à appliquer des contraintes afin d'empêcher la création d'objets factices. Peut-être voulez-vous vous assurer que tous les champs obligatoires sont fournis ou que les combinaisons de champs sont valides. Je suppose que c’est la vraie raison pour laquelle vous souhaitez introduire le séquençage dans le bâtiment.

    Dans ce cas, j'aime bien la recommandation de Joshua Bloch d'effectuer la validation dans la méthode build (). Cela facilite la validation entre les champs car tout est disponible à ce stade. Voir cette réponse: https://softwareengineering.stackexchange.com/a/241320

En résumé, je n’ajouterai aucune complication au code simplement parce que vous craignez de ne pas savoir "manquer". un appel à une méthode de générateur. En pratique, cela se fait facilement avec un cas de test. Commencez peut-être avec un constructeur vanille, puis introduisez-le si vous continuez à vous faire piquer par des appels de méthode manquants.

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