Je comprends le motif qui sous-tend le principe de moindre connaissance , mais je trouve certains inconvénients si je tente de l’appliquer dans ma conception.
L'un des exemples de ce principe (en fait, comment ne pas l'utiliser), que j'ai trouvé dans le livre Head First Design Patterns, spécifie qu'il est erroné d'appeler une méthode sur des objets renvoyés par d'autres méthodes, en termes de principe .
Mais il semble que parfois, il est très nécessaire d'utiliser une telle capacité.
Par exemple: J'ai plusieurs classes: classe de capture vidéo, classe d'encodage, classe de streamer, et elles utilisent toutes une autre classe de base, VideoFrame, et puisqu'elles interagissent les unes avec les autres, elles peuvent faire par exemple quelque chose comme ceci:
streamer
code de la classe
...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....
Comme vous pouvez le constater, ce principe n’est pas appliqué ici. Ce principe peut-il être appliqué ici, ou est-ce que ce principe ne peut pas toujours être appliqué dans une conception comme celle-ci?
la source
Réponses:
Le principe dont vous parlez (mieux connu sous le nom de loi de Demeter ) pour les fonctions peut être appliqué en ajoutant une autre méthode d'assistance à votre classe de streamer, telle que
Désormais, chaque fonction "parle uniquement à des amis", pas à des "amis d'amis".
À mon humble avis, il s’agit là d’un guide approximatif qui peut aider à créer des méthodes qui respectent plus strictement le principe de responsabilité unique. Dans un cas simple comme celui ci-dessus, il est probablement très difficile de dire si cela en vaut vraiment la peine et si le code résultant est vraiment "plus propre", ou s'il ne fait que développer votre code de manière formelle sans aucun gain notable.
la source
Le principe de moindre connaissance ou la loi de Demeter est un avertissement contre l'enchevêtrement de votre classe avec des détails sur les autres classes qui traversent couche après couche. Il vous dit qu'il est préférable de ne parler qu'avec vos "amis" et non avec les "amis des amis".
Imaginez que l'on vous demande de souder un bouclier sur la statue d'un chevalier en armure de plaques brillante. Vous placez soigneusement le bouclier sur le bras gauche pour qu'il paraisse naturel. Vous remarquez qu'il y a trois petites zones sur l'avant-bras, le coude et le haut du bras où le bouclier touche l’armure. Vous souder les trois endroits parce que vous voulez être sûr que la connexion est solide. Maintenant, imaginez que votre patron est en colère parce qu'il ne peut pas bouger le coude de son armure. Vous avez supposé que l'armure n'allait jamais bouger et a ainsi créé une connexion immobile entre l'avant-bras et le bras. Le bouclier ne devrait se connecter qu'à son ami, l'avant-bras. Pas aux amis de l'avant-bras. Même si vous devez ajouter un morceau de métal pour les faire toucher.
Les métaphores sont bien, mais qu'entendons-nous réellement par ami? Tout ce qu'un objet sait créer ou trouver est un ami. Alternativement, un objet peut simplement demander à être remis à d'autres objets , dont il ne connait que l'interface. Ceux-ci ne comptent pas comme des amis car aucune attente sur la façon de les obtenir n'est imposée. Si l'objet ne sait pas d'où il vient car quelque chose d'autre l'a injecté / injecté, alors ce n'est pas l'ami d'un ami, ce n'est même pas un ami. C'est quelque chose que seul l'objet sait utiliser. C'est une bonne chose.
Lorsque vous essayez d'appliquer de tels principes, il est important de comprendre qu'ils ne vous interdisent jamais d'accomplir quelque chose. Ils vous avertissent que vous négligez peut-être de faire plus de travail pour obtenir un meilleur design qui réalise la même chose.
Personne ne veut travailler sans raison, il est donc important de comprendre ce que vous en retirez. Dans ce cas, votre code reste flexible. Vous pouvez apporter des modifications et avoir moins de classes concernées par les modifications à prendre en compte. Cela sonne bien mais ne vous aide pas à décider quoi faire à moins que vous ne le preniez comme une sorte de doctrine religieuse.
Au lieu de suivre aveuglément ce principe, prenons une version simple de ce problème. Ecrire une solution qui ne suit pas le principe et qui fait. Maintenant que vous avez deux solutions, vous pouvez comparer leur réceptivité aux changements en essayant de les intégrer.
Si vous ne pouvez pas résoudre un problème en suivant ce principe, il vous manque probablement une autre compétence.
Une solution à votre problème particulier consiste à injecter
frame
quelque chose (une classe ou une méthode) qui sait comment parler aux images afin de ne pas avoir à étaler tous les détails de la discussion dans votre classe, qui sait seulement quand et comment obtenir un cadre.Cela découle en réalité d'un autre principe: séparer l'utilisation de la construction.
En utilisant ce code, vous avez en quelque sorte pris la responsabilité d’acquérir un
Frame
. Vous n’avez pas encore assumé la responsabilité de parler à aFrame
.Maintenant, vous devez savoir comment parler à un
Frame
, mais remplacez-le par:Et maintenant, il vous suffit de savoir comment parler à votre ami, FrameHandler.
Il existe de nombreuses façons d’y parvenir et c’est peut-être pas la meilleure, mais cela montre que le respect du principe ne rend pas le problème insoluble. C'est juste exiger plus de travail de votre part.
Toute bonne règle a une exception. Les meilleurs exemples que je connaisse sont les langages internes spécifiques à un domaine . Un DSL de la chaîne de procédésemble abuser de la loi de Demeter tout le temps parce que vous appelez constamment des méthodes qui renvoient différents types et les utilisez directement. Pourquoi ça va? Parce que dans un DSL, tout ce qui est renvoyé est un ami soigneusement conçu auquel vous devez parler directement. De par sa conception, vous avez le droit de vous attendre à ce qu'une chaîne de méthodes DSL ne change pas. Vous n’avez pas ce droit si vous plongez au hasard dans la base de code en enchaînant tout ce que vous trouvez. Les meilleurs DSL sont des représentations très fines ou des interfaces avec d'autres objets que vous ne devriez probablement pas approfondir non plus. Je n’en parle que parce que j’ai compris que je comprenais beaucoup mieux la loi de Demeter une fois que j’ai compris pourquoi les DSL sont une bonne conception. Certains vont même jusqu'à dire que les DSL n'enfreignent même pas la véritable loi de Demeter.
Une autre solution consiste à laisser autre chose
frame
vous injecter . Si vousframe
venez d'un poseur ou de préférence d'un constructeur, vous ne prenez aucune responsabilité pour la construction ou l'acquisition d'un cadre. Cela signifie que votre rôle ici est beaucoup plus semblable à ce qu'ilFrameHandlers
allait être. Au lieu de cela, c’est maintenant vous qui discutezFrame
et faites quelque chose d’autre qui permet de déterminer comment obtenir un résultat.Frame
En un sens, c’est la même solution avec un simple changement de perspective.Les principes SOLID sont les grands que j'essaie de suivre. Deux principes respectés sont les principes de responsabilité unique et d'inversion de dépendance. Il est vraiment difficile de respecter ces deux et de finir par enfreindre la loi de Demeter.
La mentalité qui consiste à violer Demeter revient à manger dans un restaurant-buffet où il faut prendre ce que vous voulez. Avec un peu de travail en amont, vous pouvez vous fournir un menu et un serveur qui vous apportera tout ce que vous voulez. Asseyez-vous, détendez-vous et donnez un bon pourboire.
la source
La conception fonctionnelle est-elle meilleure que la conception orientée objet? Ça dépend.
MVVM est-il meilleur que MVC? Ça dépend.
Amos & Andy ou Martin et Lewis? Ça dépend.
Qu'est-ce que cela dépend? Les choix que vous faites dépendent de la manière dont chaque technique ou technologie répond aux exigences fonctionnelles et non fonctionnelles de votre logiciel, tout en satisfaisant vos objectifs de conception, de performance et de maintenabilité.
Lorsque vous lisez ceci dans un livre ou un blog, évaluez la demande en fonction de son mérite. c'est pourquoi demander. Il n’ya pas de bonne ou de mauvaise technique dans le développement logiciel, il n’existe que "dans quelle mesure cette technique répond-elle à mes objectifs? Est-elle efficace ou inefficace? Résout-il un problème mais en crée-t-il un nouveau? Peut-il être bien compris par l'ensemble équipe de développement, ou est-ce trop obscur? "
Dans ce cas particulier - l'acte d'appeler une méthode sur un objet renvoyé par une autre méthode - puisqu'il existe un modèle de conception qui codifie cette pratique (Factory), il est difficile d'imaginer comment on pourrait affirmer catégoriquement qu'il s'agit d'une pratique. faux.
La raison pour laquelle on l'appelle "Principe de moindre connaissance" est que "Couplage faible" est une qualité souhaitable d'un système. Les objets qui ne sont pas étroitement liés les uns aux autres fonctionnent de manière plus indépendante et sont donc plus faciles à gérer et à modifier individuellement. Toutefois, comme le montre votre exemple, il est parfois plus souhaitable de recourir à un couplage élevé afin que les objets puissent coordonner leurs efforts plus efficacement.
la source
La réponse de Doc Brown montre une implémentation classique de Law of Demeter dans les manuels scolaires - et l'agacement / le désordre de code désorganisé consistant à ajouter des dizaines de méthodes de cette façon est probablement la raison pour laquelle les programmeurs, y compris moi-même, ne se donnent souvent pas la peine de le faire, même s'ils le devraient.
Il existe un autre moyen de découpler la hiérarchie des objets:
Dans le cas de Poster Original (OP),
encoder->WaitEncoderFrame()
renverrait unIEncoderFrame
au lieu deFrame
et définirait les opérations autorisées.SOLUTION 1
Dans le cas le plus simple,
Frame
et lesEncoder
classes sont à la fois sous votre contrôle,IEncoderFrame
est un sous - ensemble de méthodes Cadre déjà publiquement expose, et laEncoder
classe ne se soucie pas vraiment ce que vous faites à cet objet. Ensuite, la mise en œuvre est triviale ( code en c # ):SOLUTION 2
Dans un cas intermédiaire, où la
Frame
définition n'est pas sous votre contrôle, ou s'il ne serait pas approprié d'ajouterIEncoderFrame
des méthodes àFrame
, une bonne solution est alors un adaptateur . C’est ce que la réponse de CandiedOrange explique, en tant quenew FrameHandler( frame )
. IMPORTANT: si vous faites cela, il est plus flexible si vous l'exposez en tant qu'interface et non en tant que classe .Encoder
doit savoirclass FrameHandler
, mais les clients doivent seulement savoirinterface IFrameHandler
. Ou comme je l’ai nommé,interface IEncoderFrame
- pour indiquer qu’il s’agit spécifiquement de Frame vu de POV de Encoder :COST: Allocation et validation d'un nouvel objet, EncoderFrameWrapper, à chaque
encoder.TheFrame
appel. (Vous pouvez mettre en cache ce wrapper, mais cela ajoute plus de code. Et ce n'est facile de coder de manière fiable que si le champ frame de l'encodeur ne peut pas être remplacé par un nouveau frame.)SOLUTION 3
Dans le cas le plus difficile, le nouvel emballage doit connaître à la fois
Encoder
etFrame
. Cet objet violerait lui-même le LoD - il manipule une relation entre Encoder et Frame qui devrait incomber à Encoder - et il serait probablement difficile de bien faire les choses. Voici ce qui peut arriver si vous vous engagez dans cette voie:C'est devenu moche. Il existe une implémentation moins compliquée, lorsque le wrapper doit toucher les détails de son créateur / propriétaire (Encoder):
Certes, si je savais que je finirais ici, je ne ferais peut-être pas cela. Pourrait juste écrire les méthodes LoD, et en finir avec ça. Pas besoin de définir une interface. D'un autre côté, j'aime bien que l'interface encapsule les méthodes associées. J'aime ce que l'on ressent lorsque l'on fait les "opérations de type cadre" pour créer ce qui ressemble à un cadre.
COMMENTAIRES FINAUX
Considérez ceci: si le réalisateur de
Encoder
estimait que l'expositionFrame frame
était appropriée à leur architecture globale, ou était "tellement plus facile que de mettre en œuvre un LoD", il aurait été beaucoup plus sûr de se fier au premier extrait que je montre - exposer un sous-ensemble limité de Frame, en tant qu'interface. D'après mon expérience, c'est souvent une solution tout à fait réalisable. Ajoutez simplement des méthodes à l'interface selon vos besoins. (Je parle d'un scénario dans lequel nous "savons" que Frame dispose déjà des méthodes nécessaires, sinon elles seraient faciles et sans controverse. Le travail de "mise en œuvre" pour chaque méthode consiste à ajouter une ligne à la définition de l'interface.) Et sachez que même dans le pire des scénarios futurs, il est possible de faire fonctionner cette API - ici,IEncoderFrame
Frame
Encoder
.Notez également que si vous n'êtes pas autorisé à ajouter
IEncoderFrame
àFrame
, ou les méthodes nécessaires ne correspondez bien à la généraleFrame
classe, et la solution n ° 2 ne vous convient pas, peut - être à cause de la création et destruction d'objets supplémentaires, La solution n ° 3 peut être considérée simplement comme un moyen d’organiser les méthodes deEncoder
réalisation du LoD. Ne faites pas que passer par des dizaines de méthodes. Enveloppez-les dans une interface et utilisez "implémentation d'interface explicite" (si vous êtes en c #), de sorte qu'ils ne soient accessibles que lorsque l'objet est visualisé via cette interface.Un autre point que je tiens à souligner est que la décision d' exposer la fonctionnalité en tant qu'interface a traité les 3 situations décrites ci-dessus. Dans le premier,
IEncoderFrame
est simplement un sous-ensemble deFrame
la fonctionnalité de. Dans le second,IEncoderFrame
est un adaptateur. Dans le troisième,IEncoderFrame
est une partition enEncoder
fonctionnalité s. Peu importe si vos besoins changent entre ces trois situations: l'API reste la même.la source