Selon la loi de Demeter, une classe est-elle autorisée à renvoyer un de ses membres?

13

J'ai trois questions concernant la loi de Demeter.

En dehors des classes qui ont été spécifiquement désignées pour renvoyer des objets - telles que les classes d'usine et de constructeur - est-il acceptable qu'une méthode renvoie un objet, par exemple un objet détenu par l'une des propriétés de la classe ou cela violerait-il la loi de Demeter (1) ? Et si elle viole la loi de déméter, cela importerait-il si l'objet retourné est un objet immuable qui représente une donnée et ne contient que des getters pour ces données (2a)? Ou un tel ValueObject est-il un anti-modèle en soi, parce que tout ce qui est fait avec les données de cette classe se fait en dehors de la classe (2b)?

En pseudo code:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Je soupçonne que la loi de Demeter interdit un modèle tel que ci-dessus. Que puis-je faire pour m'assurer qu'on doSomethingElse()peut appeler sans violer la loi (3)?

user2180613
la source
9
Beaucoup de gens (les programmeurs fonctionnels en particulier) considèrent la loi de Déméter comme un anti-modèle. Pour d'autres, il s'agit de la «suggestion parfois utile de Demeter».
David Hammen
1
Je vais voter pour cette question car elle illustre les absurdités de la loi de Déméter. Oui, LoD est "parfois utile", mais c'est généralement stupide.
user949300
Comment xse prépare-t-on?
candied_orange
@CandiedOrange xest juste une propriété différente dont la valeur est juste un objet capable d'invoquer doSomethingElse.
user2180613
1
Votre exemple ne viole pas LOD. Le LOD consiste à empêcher les clients d'un objet de se coupler aux détails internes ou aux collaborateurs de cet objet. vous voulez éviter des choses comme user.currentSession.isLoggedIn(). car il couple les clients de usernon seulement , usermais aussi son collaborateur, session. Au lieu de cela, vous voulez pouvoir écrire user.isLoggedIn(). ceci est généralement réalisé en ajoutant une isLoggedInméthode à userlaquelle l'implémentation délègue currentSession.
Jonah

Réponses:

7

Dans de nombreux langages de programmation, toutes les valeurs renvoyées sont des objets. Comme d'autres l'ont dit, ne pas pouvoir utiliser les méthodes des objets retournés vous oblige à ne jamais rien retourner du tout. Vous devriez vous demander: "Quelles sont les responsabilités des classes A, B et C?" C'est pourquoi l'utilisation de noms de variables métasyntaxiques comme A, B et C sollicite toujours la réponse "cela dépend" car il n'y a pas de responsabilités héritées dans ces termes.

Vous voudrez peut-être examiner la conception pilotée par domaine, qui vous donnera un ensemble plus nuancé d'heuristiques pour raisonner où la fonctionnalité devrait aller et qui devrait invoquer quoi.

Votre deuxième question concernant les objets immuables concerne la notion d'
objet de valeur par rapport à un objet d'entité. en DDD, vous pouvez transmettre des objets de valeur sans presque aucune restriction. Cela ne vaut pas pour les objets d'entité.

Le LOD simpliste est beaucoup mieux exprimé en DDD que les règles d'accès aux agrégats . Aucun objet externe ne doit contenir de références à des membres d'agrégats. Seule une référence racine doit être conservée.

Mis à part DDD, vous voulez au moins développer vos propres ensembles de stéréotypes pour vos classes qui sont raisonnables pour votre domaine. Appliquez des règles sur ces stéréotypes lors de la conception de votre système.

Souvenez-vous également que toutes ces règles visent à gérer la complexité et non à vous mettre en difficulté.

Jeremy
la source
1
DDD fournit-il des règles sur le moment où les membres de la classe tels que les données peuvent être exposés (c'est-à-dire que des getters doivent exister)? Toutes les discussions autour Law of Demeter, tell, don't ask, getters are evil, object encapsulationet single responsibility principlesemblent se résumer à cette question: quand doit - délégation à un objet de domaine utilisé par opposition à la valeur de l'objet et de faire quelque chose avec elle? Il serait très utile de pouvoir appliquer des qualifications nuancées aux situations où une telle décision doit être prise.
user2180613
Des directives comme «les getters sont mauvais» existent parce que beaucoup de gens traitent les instances de classe comme des structures de données (un sac de données à transmettre). Ce n'est pas une programmation orientée objet. Les objets sont des ensembles de fonctionnalités / comportements qui fournissent réellement du travail. L'étendue des travaux est définie par la responsabilité. Ce travail va évidemment créer des objets qui sont renvoyés par les méthodes, etc. Si votre classe fait réellement un travail significatif que vous pouvez clairement définir au-delà de "représenter l'objet de données X avec les attributs Y et Z", alors vous êtes sur la bonne voie . Je n'insisterais pas dessus.
Jeremy
Pour développer sur ce point, lorsque vous utilisez beaucoup de getters / askers, cela signifie généralement que vous avez une grande méthode quelque part qui orchestre tout, que Fowler appelle un script de transaction. Avant de commencer à utiliser un getter, demandez-vous ... pourquoi est-ce que je demande ces données à l'objet? Pourquoi cette fonctionnalité n'est-elle pas dans une méthode sur l'objet? Si ce n'est pas la responsabilité de cette classe d'implémenter cette fonctionnalité, alors allez-y et utilisez un getter. Le livre [Refactoring] ( martinfowler.com/books/refactoring.html ) de Fowler est un livre sur l'heuristique de ces questions.
Jeremy
Dans son livre Implementing Domain-Driven Design, Vaughn Vernon mentionne la loi de Demeter and Tell, Don't Ask dans Ch 10, pp 382. Cela pourrait valoir la peine d'y jeter un œil si vous y avez accès.
Jeremy
6

Selon la loi de Demeter, une classe est-elle autorisée à renvoyer un de ses membres?

Oui, certainement.

Regardons les points principaux :

  • Chaque unité ne devrait avoir qu'une connaissance limitée des autres unités: seules les unités "étroitement" liées à l'unité actuelle.
  • Chaque unité ne devrait parler qu'à ses amis; ne parlez pas à des étrangers.
  • Ne parlez qu'à vos amis immédiats.

Tous les trois vous laissent poser une question: qui est un ami?

Au moment de décider quoi retourner, la loi de Déméter ou le principe de la moindre connaissance (LoD) ne dicte pas que vous vous défendez contre les codeurs qui insistent pour le violer. Cela veut dire que vous ne forcez pas les codeurs à le violer.

Obtenir ces confus est exactement pourquoi tant de gens pensent qu'un setter doit toujours retourner nul. Non. Vous devez autoriser un moyen d'effectuer des requêtes (getters) qui ne modifient pas l'état du système. C'est la séparation de requête de commande de base .

Cela signifie-t-il que vous êtes libre de vous plonger dans une base de code enchaînant ce que vous voulez? Non. N'enchaînez que ce qui devait être enchaîné. Sinon, la chaîne pourrait changer et soudainement votre matériel est cassé. C'est ce que l'on entend par amis.

De longues chaînes peuvent être conçues pour. Les interfaces fluides, iDSL, une grande partie de Java8 et le bon vieux StringBuilder sont tous destinés à vous permettre de construire de longues chaînes. Ils ne violent pas LoD parce que tout dans la chaîne est censé fonctionner ensemble et a promis de continuer à travailler ensemble. Vous violez Demeter lorsque vous enchaînez des choses qui n'ont jamais entendu parler les unes des autres. Les amis sont ceux qui ont promis de faire fonctionner votre chaîne. Les amis des amis ne l'ont pas fait.

En dehors des classes qui ont été spécifiquement désignées pour renvoyer des objets - telles que les classes d'usine et de constructeur - est-il acceptable qu'une méthode renvoie un objet, par exemple un objet détenu par l'une des propriétés de la classe ou cela violerait-il la loi de Demeter (1) ?

Cela ne crée qu'une occasion de violer Demeter. Ce n'est pas une violation. Ce n'est même pas nécessairement mauvais.

Et si elle viole la loi de déméter, cela importerait-il si l'objet retourné est un objet immuable qui représente une donnée et ne contient que des getters pour ces données (2)?

Immuable est bon mais hors de propos ici. Obtenir des trucs à travers une chaîne plus longue ne fait pas mieux. Ce qui le rend meilleur, c'est de séparer l'obtention de l'utilisation. Si vous utilisez, demandez ce dont vous avez besoin comme paramètre. N'allez pas à sa recherche en plongeant dans les getters d'étrangers.

En pseudo code:

Je soupçonne que la loi de Demeter interdit un modèle tel que ci-dessus. Que puis-je faire pour garantir que doSomethingElse () puisse être appelé sans violer la loi (3)?

Avant de parler de x.doSomethingElse(a)comprendre que vous avez écrit simplement

b.getA().doSomething()

Maintenant, LoD n'est pas un exercice de comptage de points . Mais lorsque vous créez une chaîne, vous dites que vous savez comment obtenir un A(en utilisant B) et que vous savez comment utiliser un A. Eh bien maintenant Aet Bil vaut mieux être des amis proches parce que vous venez de les coupler.

Si vous venait de demander quelque chose à la main vous une Acomme chose que vous pouvez utiliser Aet n'aurait pas pris soin d' où il vient et Bpourrait vivre une vie heureuse et libre de votre obsession à obtenir Ade B.

En ce qui concerne x.doSomethingElse(a)sans détails xsur l'origine, LoD n'a rien à dire à ce sujet.

LoD peut encourager la séparation de l'utilisation de la construction . Mais je soulignerai que si vous traitez religieusement chaque objet comme non convivial, vous serez coincé à écrire du code dans des méthodes statiques. Vous pouvez construire un graphique d'objet merveilleusement complexe de cette façon, mais vous devez finalement appeler une méthode pour démarrer la chose. Vous devez simplement décider qui sont vos amis. Il n'y a pas moyen de s'en sortir.

Alors oui, une classe est autorisée à renvoyer un de ses membres sous LoD. Lorsque vous le faites, vous devez indiquer clairement si ce membre est ami avec votre classe, car si ce n'est pas le cas, certains clients peuvent essayer de vous coupler à cette classe en vous utilisant pour l'obtenir avant de l'utiliser. C'est important car maintenant ce que vous retournez doit toujours supporter cette utilisation.

Il existe de nombreux cas où ce n'est tout simplement pas un problème. Les collections peuvent ignorer cela simplement parce qu'elles sont censées être amies avec tout ce qui les utilise. De même, les objets de valeur sont conviviaux avec tout le monde. Mais si vous avez écrit un utilitaire de validation d'adresse qui exigeait un objet employé dont il a extrait une adresse, plutôt que de simplement demander l'adresse, vous feriez mieux d'espérer que l'employé et l'adresse proviennent tous deux de la même bibliothèque.

candied_orange
la source
Les interfaces fluides ne font pas exception à LOD en raison d'une certaine notion de convivialité .... Dans une interface fluide, chacune des méthodes de la chaîne est appelée sur le même objet, car elles renvoient toutes elles-mêmes. settings.darkTheme().monospaceFont()est juste du sucre syntaxique poursettings.darkTheme(); settings.monospaceFont();
Jonah
3
@Jonah Le retour de soi est l'ultime convivialité. Et toutes les interfaces fluides ne le font pas. De nombreux iDSL sont également fluides et renvoient des types différents pour modifier les méthodes disponibles à différents points. Considérez jOOQ , constructeur étape , constructeur assistant , et mon propre cauchemardesque constructeur de monstre . Fluent est un style. Pas un modèle.
candied_orange
2

En dehors des classes qui ont été spécialement désignées pour restituer des objets [...]

Je ne comprends pas très bien cet argument. Tout objet peut renvoyer un objet à la suite d'un appel de message. Surtout si vous pensez à "tout comme objet".

Cependant, les classes sont les seuls objets qui peuvent instancier de nouveaux objets de leur propre type en leur envoyant le "nouveau" message (ou souvent remplacé par un nouveau mot-clé dans les langages POO les plus courants)


Quoi qu'il en soit, concernant votre question, je serais plutôt méfiant à propos d'un objet retournant l'une de ses propriétés afin d'être utilisé ailleurs. Il se pourrait que cet objet divulgue des informations sur son état ou ses détails d'implémentation.

Et vous ne voudriez pas que l'objet C connaisse les détails d'implémentation de B. Il s'agit d'une dépendance indésirable de l'objet A du point de vue de l'objet C.

Donc, vous voudrez peut-être vous demander si C, en connaissant A, n'en sait pas trop pour le travail qu'il a à faire, indépendamment de la LOD.

Il y a des cas où cela peut être légitime, par exemple, demander à un objet de collection pour l'un de ses éléments est approprié et ne violer aucune règle Demeter. En revanche, demander à un objet Book son objet SQLConnection est risqué et doit être évité.

Le LOD existe parce que de nombreux programmeurs ont tendance à demander des informations aux objets avant d'effectuer un travail à partir de celui-ci. LOD est là pour nous rappeler que parfois (sinon toujours) nous ne faisons que trop compliquer les choses en voulant que nos objets en fassent trop. Parfois, nous devons simplement demander à un autre objet de faire le travail pour nous. LOD est là pour nous faire réfléchir à deux fois sur l'endroit où nous plaçons le comportement dans nos objets.

NikoGJ
la source
0

Mis à part les classes qui ont été spécifiquement désignées pour renvoyer des objets - telles que les classes d'usine et de constructeur - est-il acceptable pour une méthode de renvoyer un objet?

Pourquoi ne serait-ce pas? Vous semblez suggérer qu'il est nécessaire de signaler à vos collègues programmeurs que la classe est spécifiquement destinée à renvoyer des objets, ou qu'elle ne doit pas renvoyer d'objets du tout. Dans les langues où tout est un objet, cela limiterait considérablement vos options, car vous ne seriez pas en mesure de retourner quoi que ce soit.

Robert Harvey
la source
L'exemple concerne l'implicite b.getA().doSomething()etx.doSomethingElse(b.getA())
user2180613
A a = b.getA(); a.DoSomething();- retour à un point.
Robert Harvey
2
@ user2180613 - Si vous vouliez poser des questions b.getA().doSomething()et que x.doSomethingElse(b.getA())vous auriez dû le demander explicitement. En l'état, votre question semble concerner this.b.getA().
David Hammen
1
Comme il An'est pas membre de C, ni créé Cni fourni C, il semble que l'utilisation Ade C(de quelque manière que ce soit) viole LoD. Compter des points n'est pas la raison d'être de LoD, c'est seulement un outil illustratif. Il est possible de convertir Driver().getCar().getSteeringWheel().getFrontWheels().toRight()des instructions distinctes qui contiennent chacune un point, mais la violation de LoD est toujours là. J'ai lu dans de nombreux messages sur ce forum que l'exécution d'actions devrait être déléguée à l'objet qui implémente le comportement réel, c'est-à-dire tell don't ask. Le retour des valeurs semble entrer en conflit avec cela.
user2180613
1
Je suppose que c'est vrai, mais cela ne répond pas à la question.
user2180613
0

Cela dépend de ce que vous faites. Si vous exposez un membre de votre classe, quand ce n'est l'affaire de personne que c'est un membre de votre classe, ou quel est le type de ce membre, c'est une mauvaise chose. Si vous changez ensuite le type de ce membre, et le type de la fonction renvoyant le membre, et que tout le monde doit changer son code, c'est mal.

D'un autre côté, si l'interface de votre classe indique qu'elle fournira une instance d'une classe A bien connue, ça va. Si vous avez de la chance et que vous pouvez le faire en fournissant un membre de votre classe, bon pour vous. Si vous avez changé ce membre d'un A en un B, vous devrez alors réécrire la méthode qui renvoie un A, il devra évidemment en construire un et le remplir avec les données disponibles, au lieu d'un simple retour un membre. L'interface de votre classe serait inchangée.

gnasher729
la source