web-dev-qa-db-fra.com

Pourquoi le modèle d'observateur devrait-il être déconseillé?

J'ai remarqué que mon code injecté dans les dépendances et chargé de motifs d'observateurs (en utilisant EventBus ) de Guava est souvent beaucoup plus difficile à déboguer que le code que j'ai écrit dans le passé sans ces fonctionnalités. En particulier lorsque vous essayez de déterminer quand et pourquoi le code d'observateur est appelé.

Martin Oderski et ses amis ont écrit un long article au titre particulièrement séduisant, "Deprecating the Observer Pattern" et je n'ai pas encore pris le temps de le lire.

Je voudrais savoir ce qui ne va pas avec le modèle d'observateur et tellement mieux les alternatives (proposées ou autres) pour amener des gens aussi brillants à écrire cet article.

Pour commencer, j'ai trouvé une critique (divertissante) de l'article ici .

39
Jeff Axelrod

Citant directement de le papier :

Pour illustrer les problèmes précis du modèle d'observation, nous commençons par un exemple simple et omniprésent: le déplacement de la souris. L'exemple suivant trace les mouvements de la souris lors d'une opération de glissement dans un objet Path et l'affiche à l'écran. Pour garder les choses simples, nous utilisons Scala fermetures en tant qu'observateurs.

var path: Path = null
val moveObserver = { (event: MouseEvent) =>
   path.lineTo(event.position)
   draw(path)
}
control.addMouseDownObserver { event =>
   path = new Path(event.position)
   control.addMouseMoveObserver(moveObserver)
}
control.addMouseUpObserver { event =>
   control.removeMouseMoveObserver(moveObserver)
   path.close()
   draw(path)
}

L'exemple ci-dessus, et comme nous allons le faire valoir, le modèle d'observateur tel que défini dans [25] en général, viole une gamme impressionnante de principes importants du génie logiciel:

Effets secondaires Les observateurs favorisent les effets secondaires. Étant donné que les observateurs sont apatrides, nous avons souvent besoin de plusieurs d'entre eux pour simuler une machine d'état comme dans l'exemple de traînée. Nous devons sauvegarder l'état où il est accessible à tous les observateurs impliqués, comme dans la variable path ci-dessus.

Encapsulation Comme la variable d'état path échappe à la portée des observateurs, le modèle d'observateur rompt l'encapsulation.

Composabilité Plusieurs observateurs forment une collection lâche d'objets qui traitent d'une seule préoccupation (ou de plusieurs, voir le point suivant). Étant donné que plusieurs observateurs sont installés à différents points à différents moments, nous ne pouvons pas, par exemple, les éliminer complètement.

Séparation des préoccupations Les observateurs ci-dessus non seulement tracent le chemin de la souris mais appellent également une commande de dessin, ou plus généralement, incluent deux préoccupations différentes dans le même emplacement de code . Il est souvent préférable de séparer les préoccupations de la construction du chemin et de son affichage, par exemple, comme dans le modèle modèle-vue-contrôleur (MVC) [30].

Évolutivité Nous pourrions réaliser une séparation des préoccupations dans notre exemple en créant une classe pour les chemins qui publie elle-même des événements lorsque le chemin change. Malheureusement, il n'y a aucune garantie de cohérence des données dans le modèle d'observateur. Supposons que nous créerions un autre objet de publication d'événements qui dépend des changements dans notre chemin d'origine, par exemple, un rectangle qui représente les limites de notre chemin. Considérez également un observateur écoutant les changements à la fois dans le chemin et ses limites afin de dessiner un chemin encadré. Cet observateur devra déterminer manuellement si les limites sont déjà mises à jour et, sinon, différer l'opération de dessin. Sinon, l'utilisateur pourrait observer un cadre sur l'écran qui a la mauvaise taille (un problème).

Uniformité Différentes méthodes pour installer différents observateurs diminuent l'uniformité du code.

Abstraction Il y a un faible niveau d'abstraction dans l'exemple. Il s'appuie sur une interface lourde d'une classe de contrôle qui fournit plus que des méthodes spécifiques pour installer des observateurs d'événements de souris. Par conséquent, nous ne pouvons pas faire abstraction sur les sources d'événements précises. Par exemple, nous pourrions laisser l'utilisateur abandonner une opération de glissement en appuyant sur la touche d'échappement ou utiliser un dispositif de pointeur différent tel qu'un écran tactile ou une tablette graphique.

Gestion des ressources La durée de vie d'un observateur doit être gérée par les clients. Pour des raisons de performances, nous souhaitons observer les événements de déplacement de la souris uniquement lors d'une opération de glissement. Par conséquent, nous devons explicitement installer et désinstaller l'observateur de déplacement de la souris et nous devons nous souvenir du point d'installation (contrôle ci-dessus).

Distance sémantique En fin de compte, l'exemple est difficile à comprendre car le flux de contrôle est inversé, ce qui entraîne trop de code passe-partout qui augmente la distance sémantique entre l'intention des programmeurs et le code réel.

[25] E. Gamma, R. Helm, R. Johnson et J. Vlissides. Modèles de conception: éléments d'un logiciel orienté objet réutilisable. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, États-Unis, 1995. ISBN 0-201-63361-2.

33
Jeff Axelrod

Je pense que le modèle Observer présente les inconvénients standard qui accompagnent le découplage. Le sujet est découplé d'Observer mais vous ne pouvez pas simplement regarder son code source et savoir qui l'observe. Les dépendances codées en dur sont généralement plus faciles à lire et à penser, mais elles sont plus difficiles à modifier et à réutiliser. C'est un compromis.

Quant au document, il ne traite pas du modèle Observer lui-même mais d'une utilisation particulière de celui-ci. En particulier: plusieurs objets Observer sans état par objet unique observé. Cela présente l'inconvénient évident que les observateurs séparés doivent se synchroniser les uns avec les autres ("Comme les observateurs sont apatrides, nous avons souvent besoin de plusieurs d'entre eux pour simuler une machine à états comme dans l'exemple de glissement. Nous devons enregistrer l'état où il est accessible à tous les observateurs impliqués comme dans le chemin variable ci-dessus. ")

L'inconvénient ci-dessus est spécifique à ce type d'utilisation, pas au modèle Observer lui-même. Vous pouvez également créer un seul objet observateur (avec état!) Qui implémente toutes les méthodes OnThis, OnThat, OnWhatever et vous débarrasser du problème de la simulation d'une machine d'état sur de nombreux objets apatrides.

9
Rafał Dowgird

Je serai bref car je suis nouveau sur le sujet (et je n'ai pas encore lu cet article spécifique).

Le modèle d'observateur est intuitivement erroné: l'objet à observer sait qui observe (sujet <> - observateur). C'est contre la vie réelle (dans des scénarios basés sur des événements). Si je crie, je n'ai aucune idée de qui écoute; si un éclair, frappe le sol ... la foudre ne sait pas qu'il y a un sol jusqu'à ce qu'il frappe!. Seuls les observateurs savent ce qu'ils peuvent observer.

Lorsque ce genre de choses se produit, le logiciel est un gâchis, car construit contre notre façon de penser. C'est comme si l'objet savait ce que d'autres objets pouvaient appeler ses méthodes.

IMO une couche telle que "Environnement" est celle en charge de prendre les événements et de notifier les personnes concernées. (OU mélange l'événement et le générateur de cet événement)

Event-Source (Subject) génère des événements dans l'environnement. L'environnement livre l'événement à l'observateur. L'observateur pourrait s'inscrire au type d'événements qui l'affectent ou il est en fait défini dans l'environnement. Les deux possibilités ont du sens (mais je voulais être bref).

À ma connaissance, le modèle d'observateur associe environnement et sujet.

PS. déteste mettre en paragraphes des idées abstraites! : P

6