12
votes

Pourquoi passons-nous des objets plutôt que des membres de l'objet à des fonctions?

J'ai une méthode foo () dans la classe A qui prend comme argument saisit des éléments de données de la classe B entre autres ( Disons qu'ils sont deux flotteurs et un int). La façon dont je comprends cela, il est généralement préférable de mettre en œuvre cela avec quelque chose comme: xxx

plutôt que xxx

i, mais Pas le seul, l'avantage de ceci est qu'il laisse le détail de quels membres de B pour accéder à FOO1 () et permet de masquer les détails de la mise en œuvre.

Mais ce que je me demande est de savoir si cela introduit un couplage supplémentaire entre les classes A et b . Classe A dans l'ancien cas doit connaître la classe B existe (via quelque chose comme inclure class_b_header.h ), et si les membres de B Modifier ou sont déplacés vers une classe ou une classe différente B est complètement éliminé, vous devez modifier A et foo1 () en conséquence. En revanche, dans cette dernière approche, foo2 () ne se soucie pas si la classe B existe, en fait, tout cela se soucie d'être fourni avec deux flotteurs (qui Dans ce cas, consiste en b.member1 et b.member2 ) et un int ( b.member3 ). Il y a, certainement sûr, coupler dans ce dernier exemple également, mais cet accouplement est géré par où qu'avant FOO2 () est appelé ou quelle que soit la classe accomplir sur FOO2 () , plutôt que dans la définition de a ou b .

Je suppose qu'une deuxième partie de cette question est, est-ce qu'il y a un bon moyen de découpler A et b plus loin lorsque nous voulons mettre en œuvre une solution comme FOO1 () ?


1 commentaires

Vous passez des valeurs non des classes.


8 Réponses :


18
votes

Mais ce que je me demande est de savoir si cela introduit réellement couplage entre classe A et b.

Oui, ça fait. A et b sont maintenant étroitement couplés.

Vous semblez avoir l'impression que cela est généralement admis que l'on devrait passer des objets plutôt que des membres de ces objets. Je ne sais pas comment vous avez eu cette impression, mais ce n'est pas le cas. Si vous devriez envoyer un objet ou des membres de cet objet dépend entièrement de ce que vous essayez de faire.

Dans certains cas, il est nécessaire et souhaitable d'avoir une dépendance étroitement couplée entre deux entités et, dans d'autres cas, ce n'est pas le cas. S'il y a une règle générale qui s'applique ici, je dirais que si quelque chose est l'opposé de ce que vous avez suggéré:

Éliminer les dépendances dans la mesure du possible, mais nulle part ailleurs.


0 commentaires

13
votes

Il n'y a pas vraiment une personne universellement droite et une autre qui est universellement fausse. Il s'agit essentiellement d'une question qui reflète votre véritable intention mieux (ce qui est impossible à deviner avec des variables métasyntatiques).

Par exemple, si j'ai écrit une fonction "read_person_data", elle devrait probablement prendre une sorte d'objet "personne" comme cible pour les données à lire.

Si, d'autre part, j'ai une fonction "Lire deux cordes et une INT", il devrait probablement prendre deux cordes et un int, même si (par exemple), je l'utilise pour lire une première et Nom et numéro d'employé (c'est-à-dire au moins une partie des données d'une personne).

Deux points de base Bien que: Tout d'abord, une fonction comme celle qui fait quelque chose de significatif et logique est généralement préférable à celui qui ressemble à ce dernier qui est un peu plus qu'une collection arbitraire d'actions qui se produisent ensemble.

Deuxièmement, si vous avez une situation comme celle-là, il devient ouvert à la question de savoir si la fonction en question ne devrait pas être (au moins logiquement) une partie de votre a au lieu d'être quelque chose de séparé qui fonctionne sur un a . Ce n'est certain que par aucun moyen, mais vous pouvez simplement regarder une mauvaise segmentation entre les classes en question.


0 commentaires

2
votes

Il y a plusieurs raisons pour lesquelles vous voulez transmettre une classe à la place de membres individuels.

  1. Cela dépend de ce que la fonction appelée doit faire avec les arguments. Si les arguments sont suffisamment isolés, je passerais le membre.
  2. Dans certains cas, vous devrez peut-être passer beaucoup de variables. Dans ce cas, il est plus efficace de les emballer dans un seul objet et de passer cela autour de.
  3. encapsulation. Si vous devez garder les valeurs en quelque sorte connectées les unes aux autres, vous pouvez avoir une classe de traiter avec cette association au lieu de Han le dling dans votre code où que vous ayez besoin d'un membre de celui-ci.

    Si vous êtes inquiet des dépendances aybout, vous pouvez implémenter des interfaces (comme dans Java ou la même chose en C ++ en utilisant des classes abstraites). De cette façon, vous pouvez réduire la dépendance à un objet particulier, mais en veillant à ce qu'il puisse gérer l'API requise.


0 commentaires

5
votes

Bienvenue dans le monde de l'ingénierie, où tout est un compromis et la bonne solution dépend de la manière dont vos classes et vos fonctions sont censées être utilisées (et quelle est leur sens supposé être).

si foo () est conceptuel quelque chose dont le résultat dépend d'un float , un int et un String , alors il est juste pour cela d'accepter un float , un int et une chaîne . Si ces valeurs proviennent de membres d'une classe (pourraient être B , mais pourraient également être C ou d ) ce qui n'a pas d'importance, car sémantiquement foo () n'est pas défini en termes de B .

D'autre part, si foo () est conceptuellement quelque chose dont le résultat dépend de l'état de B - par exemple, parce que Réalise une abstraction sur cet état - alors faites-la accepter un objet de type B .

Il est également vrai que la bonne pratique de programmation est également de laisser les fonctions accepter un petit nombre d'arguments si possible, je dirais jusqu'à trois sans exagération, donc si une fonction fonctionne logiquement avec plusieurs valeurs, vous voudrez peut-être vouloir Groupez ces valeurs dans une structure de données et passez des instances de cette structure de données à FOO () .

Maintenant, si vous appelez cette structure de données B , nous sommes de retour au problème d'origine - mais avec un monde de différence sémantique!

Par conséquent, que ce soit ou non foo () devrait accepter trois valeurs ou une instance de B surtout dépend de ce que foo () et < code> B concrètement méchant dans votre programme et comment ils vont être utilisés - que leurs compétences et leurs responsabilités soient logiquement couplés ou non.


0 commentaires

3
votes

Compte tenu de cette question, vous réfléchissez probablement à des classes de la mauvaise manière.

Lorsque vous utilisez une classe, vous ne devez être intéressé que par son interface publique (généralement composée uniquement de méthodes), pas dans ses membres de données. Les membres de données devraient normalement être privés pour la classe de toute façon, de sorte que vous ne les auriez même pas accès.

Pensez aux cours comme objets physiques, disons, une balle. Vous pouvez regarder la balle et voir qu'il est rouge, mais vous ne pouvez pas simplement définir la couleur de la balle pour être bleue. Vous devriez effectuer une action sur la balle pour le rendre bleu, par exemple en le peignant.

Retour à vos classes: Afin de permettre à A d'effectuer certaines actions sur B, A devra savoir quelque chose à propos de B (par exemple, que la balle doit être peinte pour changer sa couleur).

Si vous souhaitez avoir un travail avec des objets autres que ceux de la classe B, vous pouvez utiliser l'héritage pour extraire l'interface nécessaire par A en classe I, puis laisser la classe B et une autre classe C hériter de I. A Maintenant, travaillez également bien avec les deux classes B et C.


0 commentaires

0
votes

S'accorder avec le premier répondeur ici, et à votre question sur "Y a-t-il une autre solution ...";

Étant donné que vous passez dans une instance d'une classe, B, à la méthode FOO1 d'A, il est raisonnable de supposer que la fonctionnalité B fournit n'est pas totalement unique à celle-ci, c'est-à-dire qu'il pourrait y avoir d'autres moyens de mettre en œuvre quoi que ce soit Fournit à A. (si B est un pod sans logique à part entière, il ne serait même pas logique d'essayer de les découpler depuis un besoin de savoir beaucoup de B de toute façon).

Par conséquent, vous pouvez découpler un des secrets sales de B de B en élever tout ce que B est pour A à une interface, puis un "bsinterface.h" à la place. Qui suppose qu'il pourrait y avoir une c, d ... et d'autres variantes de faire ce que B fait pour A. Si pas , vous devez vous demander pourquoi B est une classe en dehors d'A en premier lieu ...

à la fin de la journée, tout tombe à une chose; Il doit avoir un sens ...

Je tournais toujours le problème sur sa tête lorsque je rencontre des débats philosophiques comme celui-ci (avec moi-même, surtout); Comment utiliserais-je A-> FOO1? Est-ce que cela a du sens, dans le code d'appel, à gérer B et que je finis toujours par y compris A et B ensemble de toute façon, à cause de la façon dont A et B sont utilisés ensemble?

I.e; Si vous voulez être pointilleux et écrire un code de nettoyage (+1 à cela), prenez un pas en arrière et appliquez la règle «Garder la simple», mais également la convivialité de renverser toutes les tentations que vous devez surmonter votre code.

Eh bien, c'est ma vue de toute façon.


0 commentaires

2
votes

Je pense que cela dépend certainement de ce que vous voulez réaliser exactement. Il n'y a rien de mal à passer quelques membres d'une classe à une fonction. Cela dépend vraiment de ce que signifie "FOO1" - fait FOO1 sur B avec autre_data_x permet de préciser ce que vous voulez faire.

Si nous prenons un exemple - au lieu d'avoir des noms arbitraires A, B, FOO et ainsi de suite, nous fabriquons des noms "réels" que nous pouvons comprendre la signification de: xxx < p> [Oui, c'est un exemple assez stupide, mais il est un peu plus logique de discuter du sujet, les objets ont des noms réels]

DrewObject1 doit savoir tout sur un Forme, et si nous commençons à faire une certaine réorganisation de la structure de données (pour stocker x et y dans une variable membre appelé point ), il a changer. Mais ce n'est probablement que quelques petits changements.

D'autre part, dans DrewObject2 , nous pouvons réorganiser tout ce que nous aimons avec la classe de forme - même supprimer tout ensemble et avoir x , y, forme et couleur dans des vecteurs distincts [pas une idée particulièrement bonne, mais si nous pensons que cela résoudra un problème quelque part, nous pouvons le faire].

Il s'agit en grande partie à ce qui a le plus de sens. Dans mon exemple, il est probablement logique de passer une forme objet à drewObject1 . Mais ce n'est pas certain d'être le cas et il y a certainement beaucoup de cas où vous ne le voudriez pas.


0 commentaires

1
votes

Pourquoi passez-vous des objets au lieu de primitives?

Ceci est un type de question que j'attendrais sur les programmeurs @ SE; Néanmoins ..

Nous passons des objets au lieu des primitifs, car nous cherchons à établir un contexte clair, distinguant un certain point de vue et exprimer une intention claire.

Choisir de passer des primitives au lieu des objets contextuels à part entière, riches et contextuels réduit le degré d'abstraction et élargit la portée de la fonction. Parfois, ce sont des objectifs, mais normalement, ils sont des choses à éviter. La faible cohésion est une responsabilité, pas un atout.

Au lieu de fonctionner sur des choses connexes via un objet riche et à part entière, avec des primitives, nous opérons maintenant sur rien de plus que des valeurs arbitraires, qui peuvent ou non être strictement liées à l'autre. Sans contexte défini, nous ne savons pas si la combinaison des primitives est valable ensemble à tout moment, encore moins dans l'état actuel du système. Toutes les protections Un objet contextuel riche aurait fourni sont perdues (ou dupliquées au mauvais endroit) lorsque nous avons choisi des primitives à la définition de la fonction lorsque nous aurions pu choisir un objet riche. En outre, tout événement ou signaux que nous souleverions normalement, observe et agirait par des modifications apportées à toutes les valeurs de l'objet contextuel riche sont plus difficiles à soulever et à tracer au bon moment lorsqu'ils travaillent avec des primitives simples.

Utiliser des objets sur les primitifs favorisant la cohésion. La cohésion est un objectif. Des choses qui appartiennent ensemble restent ensemble. Respecter la dépendance naturelle de la fonction sur ce regroupement, la commande et l'interaction des paramètres par un contexte clair est une bonne chose.

L'utilisation d'objets sur les primitives n'augmente pas nécessairement le type de couplage que nous inquiète le plus. Le type de couplage que nous devrions plus vous inquiéter est le type de couplage qui se produit lorsque des appelants externes dictent la forme et la séquence de la messagerie, et nous nous vendons davantage à leurs règles prescrites comme le seul moyen de jouer.

Au lieu de tout aller, nous devrions noter un «racontant» clair lorsque nous le voyons. Les fournisseurs de middleware et les fournisseurs de services veulent certainement que nous puissions tout aller et intégrer étroitement leur système à la nôtre. Ils veulent une dépendance ferme, étroitement imbriquée avec notre propre code, nous continuons donc de revenir. Cependant, nous sommes plus intelligents que cela. À court d'être intelligent, nous pouvons au moins être suffisamment expérimentés, après avoir subi cette route pour reconnaître ce qui se passe. Nous ne faisons pas la soumission en permettant aux éléments du code des fournisseurs d'envahir chaque recoin et de recoins, sachant que nous ne pouvons pas acheter la main car ils sont assis sur une pile de copeaux et être honnête, notre main n'est pas si bonne. Au lieu de cela, nous disons que c'est ce que je vais faire avec votre middleware, et nous établissons une interface adaptative limitée qui permet de continuer à continuer, mais ne parie pas la ferme sur cette seule main. Nous faisons cela car au cours de la prochaine main, nous pouvons faire face à un fournisseur de middleware différent ou un fournisseur de services.

Métaphor de poker ad hoc de côté, l'idée de fuir de couplage chaque fois que cela se présente va vous coûter. Courir de l'accouplement le plus imbriqué et coûteux est probablement une chose intelligente à faire si vous avez l'intention de rester dans le jeu pour la longue distance et de vous incliner que vous jouez avec d'autres fournisseurs ou fournisseurs ou appareils que vous pouvez exercer peu de contrôle.

Il y a beaucoup plus, je pourrais dire sur la relation d'objets contextuels par rapport à l'utilisation des primitives, par exemple dans la fourniture de tests significatifs et flexibles. Au lieu de cela, je suggérerais des lectures particulières sur le style de codage des auteurs comme l'oncle Bob Martin, mais je les omets pour l'instant.


0 commentaires