Donnez cet article de Dr Dobbs , et le modèle Builder en particulier, comment gérer le cas de sous-classer un Builder? En prenant une version réduite de l'exemple où nous voulons sous-classer pour ajouter l'étiquetage des OGM, une implémentation naïve serait:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Sous-classe:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Maintenant, nous pouvons écrire du code comme celui-ci:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Mais, si nous nous trompons dans la commande, tout échoue:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
Le problème est bien sûr que NutritionFacts.Builder
renvoie a NutritionFacts.Builder
, pas a GMOFacts.Builder
, alors comment résoudre ce problème, ou y a-t-il un meilleur modèle à utiliser?
Remarque: cette réponse à une question similaire offre les classes que j'ai ci-dessus; ma question concerne le problème de s'assurer que les appels du constructeur sont dans le bon ordre.
la source
build()
la sortie deb.GMO(true).calories(100)
?Réponses:
Vous pouvez le résoudre en utilisant des génériques. Je pense que c'est ce qu'on appelle les "modèles génériques curieusement récurrents"
Faites du type de retour des méthodes du générateur de classe de base un argument générique.
Maintenant, instanciez le générateur de base avec le générateur de classe dérivé comme argument générique.
la source
implements
au lieu deextends
, ou (c) tout jeter. J'ai maintenant une erreur de compilation étrange oùleafBuilder.leaf().leaf()
etleafBuilder.mid().leaf()
est OK, maisleafBuilder.leaf().mid().leaf()
échoue ...return (T) this;
génère ununchecked or unsafe operations
avertissement. C'est impossible à éviter, non?unchecked cast
avertissement, consultez la solution suggérée ci-dessous parmi les autres réponses: stackoverflow.com/a/34741836/3114959Builder<T extends Builder>
s'agit en fait d'un rawtype - cela devrait êtreBuilder<T extends Builder<T>>
.Builder
pourGMOFacts
doit également être génériqueBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
- et ce modèle peut continuer autant de niveaux que nécessaire. Si vous déclarez un générateur non générique, vous ne pouvez pas étendre le modèle.Juste pour mémoire, pour se débarrasser de la
pour l'
return (T) this;
instruction dont parlent @dimadima et @Thomas N., la solution suivante s'applique dans certains cas.Créez
abstract
le générateur qui déclare le type générique (T extends Builder
dans ce cas) et déclarezprotected abstract T getThis()
la méthode abstraite comme suit:Référez-vous à http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 pour plus de détails.
la source
build()
méthode renvoie-t-elle les NutrutionFacts ici?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
etBuilderB extends BuilderA
quand ceBuilderB
n'est pas le casabstract
Basée sur un article de blog , cette approche nécessite que toutes les classes non-feuilles soient abstraites, et toutes les classes feuilles doivent être finales.
Ensuite, vous avez une classe intermédiaire qui étend cette classe et son générateur, et autant d'autres que vous en avez besoin:
Et, enfin, une classe feuille concrète qui peut appeler toutes les méthodes du générateur sur l'un de ses parents dans n'importe quel ordre:
Ensuite, vous pouvez appeler les méthodes dans n'importe quel ordre, à partir de l'une des classes de la hiérarchie:
la source
B
, il s'avère toujours comme la classe de base.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
modèle que la classe intermédiaire SecondLevel, elle déclare à la place des types spécifiques. Vous ne pouvez pas installer une classe avant d'arriver à la feuille en utilisant les types spécifiques, mais une fois que vous le faites, vous ne pouvez plus l'étendre parce que vous utilisez les types spécifiques et que vous avez abandonné le modèle de modèle curieusement récurrent. Ce lien pourrait vous aider: angelikalanger.com/GenericsFAQ/FAQSections/…Vous pouvez également remplacer la
calories()
méthode et la laisser renvoyer le générateur d'extension. Cela se compile car Java prend en charge les types de retour covariants .la source
Il existe également une autre façon de créer des classes selon
Builder
modèle, qui se conforme à "Préférer la composition à l'héritage".Définissez une interface, cette classe parent
Builder
héritera:L'implémentation de
NutritionFacts
est presque la même (sauf pour l'Builder
implémentation de l'interface 'FactsBuilder'):Une
Builder
classe enfant doit étendre la même interface (sauf implémentation générique différente):Remarquez, c'est
NutritionFacts.Builder
un champ à l'intérieurGMOFacts.Builder
(appelébaseBuilder
). La méthode implémentée à partir de l'FactsBuilder
interface appellebaseBuilder
la méthode du même nom:Il y a aussi un grand changement dans le constructeur de
GMOFacts(Builder builder)
. Le premier appel dans le constructeur au constructeur de classe parent doit passer correctementNutritionFacts.Builder
:L'implémentation complète de la
GMOFacts
classe:la source
Un exemple complet de 3 niveaux d'héritage de plusieurs générateurs ressemblerait à ceci :
(Pour la version avec un constructeur de copie pour le constructeur, voir le deuxième exemple ci-dessous)
Premier niveau - parent (potentiellement abstrait)
Deuxième niveau
Troisième niveau
Et un exemple d'utilisation
Une version un peu plus longue avec un constructeur de copie pour le constructeur:
Premier niveau - parent (potentiellement abstrait)
Deuxième niveau
Troisième niveau
Et un exemple d'utilisation
la source
Si vous ne voulez pas attirer votre attention sur une ou trois parenthèses, ou peut-être ne pas vous sentir ... euh ... je veux dire ... toux ... le reste de votre équipe comprendra rapidement avec curiosité modèle générique récurrent, vous pouvez le faire:
supporté par
et le type de parent:
Points clés:
ÉDITER:
J'ai trouvé un moyen de contourner la création d'objet faux. Ajoutez d'abord ceci à chaque constructeur:
Puis dans le constructeur pour chaque constructeur:
Le coût est un fichier de classe supplémentaire pour la
new Object(){}
classe interne anonymela source
Une chose que vous pouvez faire est de créer une méthode de fabrique statique dans chacune de vos classes:
Cette méthode de fabrique statique renverrait alors le générateur approprié. Vous pouvez avoir une
GMOFacts.Builder
extension aNutritionFacts.Builder
, ce n'est pas un problème. LE problème ici sera de gérer la visibilité ...la source
La contribution IEEE suivante Refined Fluent Builder en Java offre une solution complète au problème.
Il dissèque la question originale en deux sous-problèmes de déficience d'héritage et de quasi invariance et montre comment une solution à ces deux sous-problèmes s'ouvre pour le support de l'héritage avec la réutilisation du code dans le modèle de constructeur classique en Java.
la source
J'ai créé une classe de générateur générique abstraite parente qui accepte deux paramètres de type formel. Le premier concerne le type d'objet retourné par build (), le second est le type renvoyé par chaque paramètre optionnel. Vous trouverez ci-dessous des cours pour parents et enfants à des fins d'illustration:
Celui-ci a répondu à mes besoins avec satisfaction.
la source