Lois (in) cassables du code cool: La loi de Demeter (avec des exemples en TypeScript)

Quand j'ai appris ces principes, la flexibilité de mon code s'est sentie x2, et la vitesse de prise de décision sur la conception d'entité x5.



Si SOLID est un ensemble de principes pour écrire du code de qualité, alors Law of Demeter (LoD) et Tell Don't Ask (TDA) sont des astuces spécifiques pour obtenir SOLID.



Aujourd'hui, nous allons parler de la loi de Déméter ("Loi de Déméter").



Exagéré



Ce principe aide à déterminer: "Comment vais-je obtenir / modifier les objets imbriqués" - applicable dans les langages où vous pouvez définir des "classes" avec des propriétés et des méthodes.



Une situation survient souvent lorsque nous de quelque part (par exemple, à partir d'une requête HTTP) avons obtenu l'identifiant de l'entité ʻa`, nous l'avons suivi dans la base de données et de l'entité ʻa` nous devons obtenir / changer l'entité `b` en appelant la méthode` Method`.



Alors Wikipédia dit:

Le code ʻabMethod () `viole la loi de Demeter, et le code ʻa.Method ()` est correct.


Exemple



L'utilisateur a des messages qui ont des commentaires. Vous souhaitez recevoir les "Commentaires du dernier message".



Vous pouvez déposer ceci:



const posts = user.posts
const lastPostComments = posts[posts.length-1].comments


Ou comme ça:



const userLastPostComments = user.getPosts().getLast().getComments()


Problème: le code connaît toute la hiérarchie des données imbriquées et si cette hiérarchie change / s'agrandit, quel que soit l'endroit où cette chaîne a été appelée, vous devrez apporter des modifications (refactoriser le code + tests).



Pour résoudre le problème, appliquez LoD:



const userLastPostComments = user.getLastPostComments()


Mais déjà dans ` User`, nous écrivons:



class User {
  // ...
  getLastPostComments(): Comments {
    return this.posts.getLastComments()
  }
  // ...
}


La même histoire avec un commentaire ajouté. Nous sommes de:



const newComment = new Comment(req.body.postid, req.body.content)
user.getPosts().addComment(newComment)


Ou, si vous souhaitez montrer votre diagnostic:



const newComment = new Comment(req.body.postid, req.body.content)
const posts = user.posts
posts[posts.length-1].comments.push(newComment)


Transformer cela en ceci:



const posts = user.addCommentToPost(req.body.postid, req.body.content)


Et pour « Utilisateur» :



class User {
  // ...
  addCommentToPost(postId: string, content: string): void {
    // The cleanest
    const post = this.posts.getById(postId)
    return post.addComment(content)
  }
  // ...
}




Clarification: ` new Comment` peut être créé en dehors de` user` ou ` post` , tout dépend de la façon dont la logique de votre application est organisée, mais plus il est proche du propriétaire de l'entité (dans ce cas,` post` ), mieux c'est.



Qu'est ce que ça fait?



Nous masquons les détails de l'implémentation, et si jamais il y a une extension de changement / hiérarchie (par exemple, les positions ne se trouveront pas seulement dans la `propriété des articles '), vous n'avez qu'à refactoriser la méthode` getLastPostComments` / ` addCommentToPost` et réécrire un test unitaire uniquement cette méthode.



Quels sont les inconvénients



Beaucoup d'extras code.



Dans les petits projets, la plupart des méthodes seront simplement ` getter` /` setter` .



Quand utiliser



(1) LoD convient aux modèles, entités, agrégats ou classes qui ont des connexions profondes / complexes / fusionnées.



(2) Le code nécessite le concept: "Obtenez les commentaires du dernier message" - et vos messages ne sont pas dans la 1ère propriété, mais dans 2 ou plus, alors, bien sûr, vous devez faire la méthode ` getLastPostComments` et fusionner plusieurs propriétés avec différentes des postes.



(3) J'essaie d'utiliser ce principe aussi souvent que possible lorsqu'il s'agit de transformer (modifier, créer, supprimer) des données. Et moins souvent lorsqu'il s'agit de récupérer des données.



(4) Basé sur le bon sens.



Piratage de la vie



Beaucoup ont commencé à s'inquiéter du nombre de méthodes proxy, alors voici une simplification:



afin de ne pas créer un nombre infini de méthodes proxy, LoD peut être utilisé dans la classe de premier niveau (dans notre cas ʻUser`), et dans l'implémentation, il enfreint déjà la loi. Par exemple:



const userLastPostComments = user.getLastPostComments()

class User {
  // ...
  getLastPostComments(): Comments {
    //   LoD,    Post    
    const lastPost = this.posts[this.posts.length-1]
    return lastPost.comments
  }
  // ...
}




Avec le temps, si `post` grandit, il sera possible de le faire` getLast ()` ou` getLastComments () `et cela n'entraînera pas beaucoup de refactoring.



Ce qui est nécessaire pour cela



LoD fonctionne bien si vous avez une arborescence / hiérarchie de dépendances d'entité appropriée .



Que lire



(1) https://qna.habr.com/q/44822

Assurez-vous de lire tous les commentaires et commentaires des commentaires (avant le commentaire Vyacheslav GolovanovSLY_G), en se souvenant qu'il existe à la fois des exemples corrects et incorrects

(2) https://ru.wikipedia.org/wiki/Zakon_Demeter

(3) Article



PS



Je pourrais bousiller avec quelques détails / exemples ou expliquer que ce n'est pas assez clair, alors écrivez dans les commentaires que vous avez remarqués, je vais apporter des modifications. Tout bon.



PPS



Lisez cette ligne de commentaires , dans laquelle nousrepaire nous analysons un cas incomplètement éclairé plus en détail dans cet article



All Articles