web-dev-qa-db-fra.com

Passer un seul objet ou passer plusieurs paramètres

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?

26
Rufus

La méthode A (paramètres nus) présente toujours les avantages

  • il nécessite que l'auteur de la méthode tape moins, car ils n'ont pas à implémenter un objet de paramètre,
  • il nécessite que l'appelant de méthode tape moins, car ils n'ont pas à instancier un objet de paramètre
  • il fonctionne mieux, car aucun objet de paramètre ne doit être construit et récupéré
  • le lecteur peut voir quels sont les paramètres individuels à partir de la signature de la méthode seule (mais c'est une épée à double tranchant; voir ci-dessous)

La méthode B ( Parameter Object ) présente des avantages lorsque

  • les paramètres ont une signification de domaine en tant que groupe, de sorte que l'objet de paramètre peut recevoir un nom qui explique cette signification, évitant au lecteur d'avoir à lire et à comprendre chaque membre du groupe et comment ils se rapportent
  • la liste des paramètres est utilisée dans plusieurs méthodes, donc l'utilisation de l'objet paramètre dans chacune réduit la duplication
  • les valeurs de la liste de paramètres sont transmises entre plusieurs méthodes en tant que groupe, ce qui est plus facile lorsqu'elles peuvent être transmises en tant qu'objet de paramètre unique
  • certaines combinaisons de valeurs ne sont pas valides; l'objet paramètre peut empêcher ces combinaisons
  • certaines valeurs sont facultatives, qui peuvent être fournies par l'objet paramètre au lieu (selon votre langue) de valeurs de paramètre par défaut ou de méthodes surchargées
  • il y a plus d'un paramètre du même type, ce qui rend les erreurs d'échange de valeur plus probables (bien qu'un objet de paramètre ne soit pas mieux dans ce cas s'il a un constructeur avec la même liste de paramètres que la méthode)

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

  • presque jamais utile pour un seul paramètre, parfois utile pour une méthode à deux paramètres (par exemple, Point vaut généralement mieux que x, y) et parfois non, et de plus en plus utile avec trois paramètres ou plus
  • de plus en plus utile lorsque plusieurs méthodes utilisent la même liste de paramètres
32
Dave Schweisguth

Parameter Objects 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 Strings ou Ints 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

7
Som Bhattacharyya

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).

1
LongChalk

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.

1
Gordon Bean