J'utilise une bibliothèque tierce. Ils me passent un POJO qui, à nos fins et à des fins, est probablement implémenté comme ceci:
public class OurData {
private String foo;
private String bar;
private String baz;
private String quux;
// A lot more than this
// IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
OurData(/* I don't know what they do */) {
// some stuff
}
public String getFoo() {
return foo;
}
// etc.
}
Pour de nombreuses raisons, notamment, mais sans s'y limiter, l'encapsulation de leur API et la facilitation des tests unitaires, je souhaite encapsuler leurs données. Mais je ne veux pas que mes classes de base dépendent de leurs données (encore une fois, pour des raisons de test)! Alors maintenant, j'ai quelque chose comme ça:
public class DataTypeOne implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
}
public class DataTypeTwo implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz, String quux) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
this.quux = quux;
}
}
Et puis ceci:
public class ThirdPartyAdapter {
public static makeMyData(OurData data) {
if(data.getQuux() == null) {
return new DataTypeOne(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
);
} else {
return new DataTypeTwo(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
data.getQuux();
);
}
}
Cette classe d'adaptateur est couplée aux quelques autres classes qui DOIVENT connaître l'API tierce, ce qui limite son omniprésence dans le reste de mon système. Cependant ... cette solution est BRUTE! Dans Clean Code, page 40:
Plus de trois arguments (polyadiques) nécessitent une justification très spéciale - et ne devraient donc pas être utilisés de toute façon.
Choses que j'ai envisagées:
- Créer un objet d'usine plutôt qu'une méthode d'assistance statique
- Ne résout pas le problème d'avoir un bajillion d'arguments
- Création d'une sous-classe de DataTypeOne et DataTypeTwo qui a un constructeur dépendant
- A toujours un constructeur protégé polyadique
- Créez des implémentations entièrement distinctes conformes à la même interface
- Plusieurs des idées ci-dessus simultanément
Comment gérer cette situation?
Notez que ce n'est pas une situation de couche anti-corruption . Il n'y a rien de mal avec leur API. Les problèmes sont:
- Je ne veux pas que MES structures de données aient
import com.third.party.library.SomeDataStructure;
- Je ne peux pas construire leurs structures de données dans mes cas de test
- Ma solution actuelle se traduit par un nombre d'arguments très très élevé. Je veux garder le nombre d'arguments bas, SANS passer dans leurs structures de données.
- Cette question est " qu'est - ce qu'une couche anti-corruption?". Ma question est " comment puis-je utiliser un modèle, n'importe quel modèle, pour résoudre ce scénario?"
Je ne demande pas non plus de code (sinon cette question serait sur SO), je demande juste assez de réponse pour me permettre d'écrire le code efficacement (ce que cette question ne fournit pas).
la source
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Réponses:
La stratégie que j'ai utilisée quand il y a plusieurs paramètres d'initialisation est de créer un type qui ne contient que les paramètres d'initialisation
Ensuite, le constructeur de DataTypeTwo prend un objet DataTypeTwoParameters, et DataTypeTwo est construit via:
Cela donne beaucoup d'occasions de préciser quels sont tous les paramètres entrant dans DataTypeTwo et ce qu'ils signifient. Vous pouvez également fournir des valeurs par défaut raisonnables dans le constructeur DataTypeTwoParameters afin que seules les valeurs qui doivent être définies puissent être effectuées dans l'ordre que le consommateur de l'API aime.
la source
Integer.parseInt
? Dans un setter, ou en dehors de la classe des paramètres?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
àDataTypeTwo
.Vous avez vraiment deux préoccupations distinctes ici: encapsuler une API et maintenir le nombre d'arguments bas.
Lors de l'encapsulation d'une API, l'idée est de concevoir l'interface comme à partir de zéro, ne connaissant rien d'autre que les exigences. Vous dites qu'il n'y a rien de mal avec leur API, puis dans la même liste de souffle un certain nombre de choses qui ne vont pas avec leur API: testabilité, constructibilité, trop de paramètres dans un objet, etc. Écrivez l'API que vous souhaitez avoir. Si cela nécessite plusieurs objets au lieu d'un, faites-le. Si cela nécessite un habillage d'un niveau supérieur, vers les objets qui créent le POJO, faites-le.
Ensuite, une fois que vous avez votre API souhaitée, le nombre de paramètres peut ne plus être un problème. Si tel est le cas, il existe un certain nombre de modèles communs à considérer:
Notez que ces modèles de création finissent souvent par appeler un constructeur polyadique, ce que vous devriez considérer correct lorsqu'il est encapsulé. Le problème avec les constructeurs polyadiques n'est pas de les appeler une fois, c'est quand vous êtes obligé de les appeler chaque fois que vous avez besoin de construire un objet.
Notez qu'il est généralement beaucoup plus facile et plus facile de passer à l'API sous-jacente en stockant une référence à l'
OurData
objet et en transférant les appels de méthode, plutôt que d'essayer de réimplémenter ses internes. Par exemple:la source
OurData
objet» - c'est ce que j'essaie d'éviter, au moins dans la classe de base, pour s'assurer qu'il n'y a pas de dépendance.DataInterface
. Vous créez une autre implémentation pour vos objets fictifs.Je pense que vous interprétez peut-être trop strictement la recommandation de l'oncle Bob. Pour les classes normales, avec la logique et les méthodes et constructeurs et autres, un constructeur polyadique ressemble en effet beaucoup à l'odeur du code. Mais pour quelque chose qui est strictement un conteneur de données qui expose des champs, et qui est déjà généré par ce qui est essentiellement un objet Factory, je ne pense pas que ce soit trop mauvais.
Vous pouvez utiliser le modèle d'objet de paramètre, comme suggéré dans un commentaire, peut envelopper ces paramètres de constructeur pour vous, ce qu'est votre encapsuleur de type de données local est déjà , essentiellement, un objet de paramètre. Tout ce que votre objet Parameter fera, c'est emballer les paramètres (comment allez-vous les créer? Avec un constructeur polyadique?), Puis les décompresser une seconde plus tard dans un objet qui est presque identique.
Si vous ne voulez pas exposer les setters pour vos champs et les appeler, je pense que s'en tenir à un constructeur polyadique à l'intérieur d'une usine bien définie et encapsulée est très bien.
la source