Supposons que j'ai les éléments suivants
Class A {
Foo getFoo();
Bar getBar();
Baz getBaz();
}
Et je dois définir une fonction doStuff
qui utilise Foo
, Bar
, Baz
of one object et fait quelques trucs
Je me bats entre la méthode d'implémentation de doStuff
qui est la meilleure (supposons qu'il ne serait pas souhaitable de placer doStuff
dans la classe A
)
Méthode A
void doStuff(Foo foo, Bar bar, Baz baz)
{
//some operation
}
ou
Méthode B
void doStuff(A a)
{
Foo foo = a.getFoo();
Bar bar = a.getBar();
Baz baz = a.getBaz();
//some operation
}
À ma connaissance limitée, (+ pour, - contre)
Méthode A
+ Il est clair exactement sur quels paramètres doStuff()
fonctionne
-Sensible aux longues listes de paramètres et plus sensible aux erreurs de l'utilisateur
Méthode B
+ Méthode simple et facile à utiliser
+ Semble plus extensible (?)
-Crée une dépendance inutile envers la classe A
Quelqu'un peut-il partager des informations supplémentaires sur les avantages et les inconvénients de ces deux méthodes?
La méthode A (paramètres nus) présente toujours les avantages
La méthode B ( Parameter Object ) présente des avantages lorsque
Le fait que l'objet paramètre introduise une nouvelle dépendance dont dépendent l'appelant et l'appelé n'est pas vraiment un inconvénient, car il s'agit d'une classe simple sans aucune dépendance propre.
Ainsi, l'objet de paramètre est
Parameter Object
s fournit une approche intéressante pour encapsuler les paramètres related
afin de réduire le nombre total de paramètres à n'importe quelle méthode ou constructeur. Il faut être très prudent pour s'assurer que les objets paramètres contiennent réellement des paramètres réellement liés.
En fait, il existe plusieurs façons d'aborder ce problème en fonction du parameter types
vous avez affaire. Si vous traitez avec des paramètres qui sont des types généraux comme plus d'un String
s ou Int
s et qu'il y a un potentiel pour qu'un client passe réellement dans la mauvaise séquence d'arguments, cela a souvent plus de sens créer custom types
c'est à dire. créer enum
avec des valeurs possibles. Cela peut fournir une bonne vérification du temps de compilation pour vos arguments. Vous pouvez également les utiliser pour return
des valeurs complexes à partir de fonctions. Voir ici .
Une autre approche que je prends souvent est de vérifier et de voir si le travail effectué par la méthode doStuff
se décompose en méthodes plus simples avec moins de dépendances.
J'essaie principalement de suivre la recommandation de Bob Martin d'un maximum de trois paramètres. Eh bien, il dit en fait qu'il ne devrait pas y en avoir plus d'un! Toute augmentation devrait avoir des raisons justifiées. Référez-vous à cet excellent livre: Clean Code
Considérez un Client qui a un Adresse et un CurrentInvoice. Ce qui est plus correct -
SendInvoiceToAddress(Invoice invoice, Address adress);
ou
SendInvoiceToAddress(Customer customer);
Je pense les deux. Ou autrement dit - cela dépend vraiment de votre application.
Si chaque facture appartient (par définition) à un seul client, c'est une chose. et cela impliquerait que votre méthode existe dans la classe CustomerInvoiceSender (ou quelque chose comme ça). C'est quelque chose qui appartient entièrement au domaine client. Chaque mois, vous souhaitez envoyer la facture de ce mois (et rien d'autre).
Si vous souhaitez envoyer plusieurs factures à plusieurs adresses (pas nécessairement pour les clients, à quelque fin que ce soit), eh bien, c'est une tout autre histoire. Il existerait probablement également dans la classe InvoiceSender (ou quelque chose comme ça). Cela n'a également rien à voir avec le domaine Client. Dans ce cas, les clients ne sont qu'un petit cas de factures expédiées. Il convient également de noter que dans ce cas, vous souhaiterez peut-être des interfaces au lieu de bétons car la facture d'un client et que la facture d'une entreprise peut être deux classes très différentes qui se trouvent juste partager une interface commune ("quelques propriétés" dans ce cas).
Les réponses de David et Som contiennent toutes deux d'excellentes informations. J'ajouterai ce qui suit:
Comme pour de nombreux modèles de conception, la décision à prendre repose sur un continuum entre les options avec leurs propres avantages et inconvénients. Il n'y a pas toujours une bonne réponse - le plus souvent, il s'agit de déterminer les avantages que vous souhaitez apprécier et les inconvénients que vous êtes prêt à risquer.
D'après mon expérience, le passage à un DTO est utile lorsque vous avez liées valeurs qui toujours voyagent ensemble. David a bien décrit les avantages de cette approche. Un autre inconvénient que j'ai constaté dans cette approche est que vous risquez d'ajouter des dépendances inutiles aux méthodes lorsque le DTO grandit.
Par exemple, les méthodes A, B, C et D prennent Foo, Bar et Baz, il est donc agréable de combiner ces arguments dans un DTO. Ensuite, les méthodes A et B doivent prendre Quux - ajoutez-vous Quux au DTO forçant C et D à prendre une dépendance inutilisée? Lorsque vous testez C et D, quelle valeur transmettez-vous pour Quux? Lorsqu'un nouveau développeur utilise les méthodes C et D, la présence de Quux génère-t-elle de la confusion? En comparant les méthodes A et C, est-il clair comment définir ou non Quux?
Des situations similaires se produisent lorsque vous avez initialement besoin de Foo, Bar et Baz pour toutes les méthodes, mais que certaines méthodes n'ont plus besoin de ces valeurs.
J'ai observé une expérience où une équipe passait un DTO au service d'une autre équipe et faisait de grands efforts pour remplir et synchroniser correctement les informations dans ce DTO, alors que tout ce qui était réellement nécessaire était une seule valeur qui aurait pu être transmise trivialement.
Si les valeurs ne vont pas toujours ensemble, vous risquez de générer de la confusion, une charge de test accrue et un travail de développement supplémentaire. Si les valeurs vont toujours ensemble, un DTO peut apporter de la clarté, réduire la duplication, simplifier la cohérence, etc.