J'essaie d'apprendre GRASP et j'ai trouvé cela expliqué ( ici à la page 3 ) sur le couplage bas et j'ai été très surpris quand j'ai trouvé ceci:
Considérez la méthode
addTrack
pour uneAlbum
classe, deux méthodes possibles sont:
addTrack( Track t )
et
addTrack( int no, String title, double duration )
Quelle méthode réduit le couplage? La seconde le fait, car la classe utilisant la classe Album n'a pas besoin de connaître une classe Track. En général, les paramètres des méthodes doivent utiliser les types de base (int, char ...) et les classes des packages java. *.
J'ai tendance à ne pas être d'accord avec cela; Je pense que addTrack(Track t)
c'est mieux que addTrack(int no, String title, double duration)
pour diverses raisons:
Il est toujours préférable pour une méthode d'avoir le moins de paramètres possible (selon le code propre de l'oncle Bob, aucun ou un de préférence, 2 dans certains cas et 3 dans des cas spéciaux; plus de 3 doivent être refactorisés - ce sont bien sûr des recommandations et non des règles holistiques) .
Si
addTrack
est une méthode d'une interface, et que les conditions requisesTrack
doivent avoir plus d'informations (par exemple l'année ou le genre), l'interface doit être modifiée et la méthode doit donc prendre en charge un autre paramètre.L'encapsulation est rompue; si
addTrack
est dans une interface, alors il ne devrait pas connaître les internes duTrack
.Il est en fait plus couplé dans la deuxième manière, avec de nombreux paramètres. Supposons que le
no
paramètre doit être changé deint
àlong
car il y a plus deMAX_INT
pistes (ou pour une raison quelconque); alors laTrack
et la méthode doivent être changées tandis que si la méthode était laaddTrack(Track track)
seuleTrack
serait changée.
Tous les 4 arguments sont en fait liés les uns aux autres, et certains d'entre eux sont des conséquences d'autres.
Quelle approche est la meilleure?
Réponses:
Eh bien, vos trois premiers points concernent en fait d'autres principes que le couplage. Vous devez toujours trouver un équilibre entre des principes de conception souvent contradictoires.
Votre quatrième point est au sujet de couplage, et je suis d' accord avec vous fortement. Le couplage concerne le flux de données entre les modules. Le type de conteneur dans lequel les données circulent est largement immatériel. Passer une durée sous la forme d'un double au lieu d'un champ d'un
Track
ne supprime pas la nécessité de la passer. Les modules doivent toujours partager la même quantité de données et ont toujours la même quantité de couplage.Il ne parvient pas non plus à considérer tous les couplages du système comme un agrégat. Bien que l'introduction d'une
Track
classe ajoute certes une autre dépendance entre deux modules individuels, elle peut réduire considérablement le couplage du système , ce qui est la mesure importante ici.Par exemple, considérez un bouton "Ajouter à la liste de lecture" et un
Playlist
objet. L'introduction d'unTrack
objet peut être envisagée pour augmenter le couplage si vous ne considérez que ces deux objets. Vous avez maintenant trois classes interdépendantes au lieu de deux. Cependant, ce n'est pas l'intégralité de votre système. Vous devez également importer la piste, lire la piste, afficher la piste, etc. L'ajout d'une classe de plus à ce mixage est négligeable.Envisagez maintenant de devoir ajouter la prise en charge de la lecture des pistes sur le réseau plutôt que localement. Il vous suffit de créer un
NetworkTrack
objet conforme à la même interface. Sans l'Track
objet, il faudrait créer partout des fonctions comme:Cela double efficacement votre couplage, nécessitant même des modules qui ne se soucient pas des éléments spécifiques au réseau pour néanmoins en garder la trace, afin de pouvoir le transmettre.
Votre test d'effet d'entraînement est un bon test pour déterminer votre véritable quantité de couplage. Ce qui nous préoccupe, c'est de limiter les endroits où un changement affecte.
la source
Ma recommandation est:
Utilisation
mais assurez-vous qu'il
ITrack
s'agit d'une interface et non d'une classe concrète.Album ne connaît pas les composants internes des
ITrack
implémenteurs. Il est uniquement couplé au contrat défini par laITrack
.Je pense que c'est la solution qui génère le moins de couplage.
la source
Track
fait d' être stupide ou intelligent.Track
est une concrétion.ITrack
l'interface est une abstraction. De cette façon, vous pourrez avoir différents types de pistes à l'avenir, à condition qu'elles soient conformesITrack
.Je dirais que le deuxième exemple de méthode augmente très probablement le couplage, car il est très probable qu'il instancie un objet Track et le stocke dans l'objet Album actuel. (Comme suggéré dans mon commentaire ci-dessus, je suppose qu'il est inhérent qu'une classe Album ait le concept d'une classe Track quelque part à l'intérieur.)
Le premier exemple de méthode suppose qu'une piste est instanciée en dehors de la classe Album, donc à tout le moins, nous pouvons supposer que l' instanciation de la classe Track n'est pas couplée à la classe Album.
Si les meilleures pratiques suggéraient que nous n'avons jamais une référence de classe à une seconde classe, l'intégralité de la programmation orientée objet serait rejetée par la fenêtre.
la source
Le couplage n'est qu'un des nombreux aspects à essayer d'obtenir dans votre code. En réduisant le couplage, vous n'améliorez pas nécessairement votre programme. En général, c'est une meilleure pratique, mais dans ce cas particulier, pourquoi ne devrait-on pas
Track
le savoir?En utilisant une
Track
classe à laquelle passerAlbum
, vous rendez votre code plus facile à lire, mais plus important encore, comme vous l'avez mentionné, vous transformez une liste statique de paramètres en un objet dynamique. Cela rend finalement votre interface beaucoup plus dynamique.Vous mentionnez que l'encapsulation est rompue, mais ce n'est pas le cas.
Album
doit connaître les éléments internes deTrack
, et si vous n'utilisez pas un objet,Album
devrait connaître chaque élément d'information qui lui est transmis avant de pouvoir l'utiliser tout de même. L'appelant doit également connaître les éléments internesTrack
, car il doit construire unTrack
objet, mais l'appelant doit tout de même connaître ces informations si elles sont transmises directement à la méthode. En d'autres termes, si l'avantage de l'encapsulation n'est pas de connaître le contenu d'un objet, il ne pourrait pas être utilisé dans ce cas car ilAlbum
doit utiliserTrack
les informations de la même façon.Si vous ne souhaitez pas utiliser,
Track
c'est siTrack
contient une logique interne à laquelle vous ne souhaitez pas que l'appelant ait accès. En d'autres termes, siAlbum
une classe devait utiliser un programmeur utilisant votre bibliothèque, vous ne voudriez pas qu'il l'utiliseTrack
si vous l'utilisez pour dire, appelez une méthode pour la conserver dans la base de données. Le vrai problème avec cela réside dans le fait que l'interface est enchevêtrée avec le modèle.Pour résoudre le problème, vous devez vous séparer
Track
en ses composants d'interface et ses composants logiques, créant deux classes distinctes. Pour l'appelant,Track
devient une classe légère qui est destinée à contenir des informations et à offrir des optimisations mineures (données calculées et / ou valeurs par défaut). À l'intérieurAlbum
, vous utiliseriez une classe nomméeTrackDAO
pour effectuer le gros du travail associé à l'enregistrement des informationsTrack
dans la base de données.Bien sûr, ce n'est qu'un exemple. Je suis sûr que ce n'est pas du tout votre cas, et n'hésitez donc pas à utiliser sans
Track
culpabilité. N'oubliez pas de garder votre appelant à l'esprit lorsque vous construisez des classes et de créer des interfaces si nécessaire.la source
Les deux sont corrects
est mieux (comme vous l'avez déjà fait valoir) tout en
est moins couplé car le code qui utilise
addTrack
n'a pas besoin de savoir qu'il existe uneTrack
classe. La piste peut être renommée par exemple sans avoir à mettre à jour le code appelant.Pendant que vous parlez de code plus lisible / maintenable, l'article parle de couplage . Un code moins couplé n'est pas nécessairement plus facile à implémenter et à comprendre.
la source
Un faible couplage ne signifie pas aucun couplage. Quelque chose, quelque part, doit être connu sur les objets ailleurs dans la base de code, et plus vous réduisez la dépendance aux objets "personnalisés", plus vous donnez de raisons pour que le code change. Ce que l'auteur que vous citez promeut avec la deuxième fonction est moins couplé, mais aussi moins orienté objet, ce qui est contraire à l'idée de GRASP comme étant une méthodologie de conception orientée objet . L'essentiel est de savoir comment concevoir le système comme une collection d'objets et leurs interactions; les éviter, c'est comme vous apprendre à conduire une voiture en disant que vous devriez plutôt faire du vélo.
Au lieu de cela, la bonne voie consiste à réduire la dépendance à l'égard des objets concrets , qui est la théorie du «couplage lâche». Moins une méthode doit connaître de types concrets définis, mieux c'est. Juste par cette déclaration, la première option est en fait moins couplée, car la deuxième méthode prenant les types les plus simples doit connaître tous ces types plus simples. Bien sûr, ils sont intégrés et le code à l'intérieur de la méthode peut avoir à faire attention, mais la signature de la méthode et les appelants de la méthode ne le font certainement pas . La modification de l'un de ces paramètres relatifs à une piste audio conceptuelle va nécessiter plus de changements lorsqu'ils sont séparés par rapport à lorsqu'ils sont contenus dans un objet Track (qui est le point des objets; encapsulation).
En allant un peu plus loin, si Track devait être remplacé par quelque chose qui faisait mieux le même travail, une interface définissant les fonctionnalités requises serait peut-être en règle, un ITrack. Cela pourrait permettre différentes implémentations telles que "AnalogTrack", "CdTrack" et "Mp3Track" qui fournissaient des informations supplémentaires plus spécifiques à ces formats, tout en fournissant l'exposition de données de base d'ITrack qui représente conceptuellement une "piste"; un sous-morceau fini de l'audio. Track pourrait également être une classe de base abstraite, mais cela vous oblige à toujours vouloir utiliser l'implémentation inhérente à Track; réimplémentez-le comme BetterTrack et maintenant vous devez modifier les paramètres attendus.
Ainsi la règle d'or; les programmes et leurs composants de code auront toujours des raisons de changer. Vous ne pouvez pas écrire un programme qui ne nécessitera jamais de modifier le code que vous avez déjà écrit afin d'ajouter quelque chose de nouveau ou de modifier son comportement. Votre objectif, dans n'importe quelle méthodologie (GRASP, SOLID, tout autre acronyme ou mot à la mode auquel vous pouvez penser) est simplement d'identifier les choses qui devront changer au fil du temps, et de concevoir le système afin que ces changements soient aussi faciles à effectuer que possible (traduit; toucher le moins de lignes de code et affecter le moins possible d'autres domaines du système au-delà de la portée de la modification envisagée). Par exemple, ce qui est le plus susceptible de changer, c'est qu'une piste gagnera plus de membres de données dont addTrack () peut ou non se soucier, non cette piste sera remplacée par BetterTrack.
la source