10
votes

Aidez-moi à supprimer un singleton: à la recherche d'une alternative

Contexte: J'ai quelques classes mettant en œuvre un modèle de conception sujet / observateur que j'ai fabriqué sur le fil. Un Sujet notifiera que c'est observateurs par un appel de méthode simple Observer-> notifié (ceci) si le Observer était construit dans le même fil que la notification est en cours. Mais si le observateur a été construit dans un thread différent, la notification sera affichée sur une file d'attente pour être traitée ultérieurement par le fil qui construit le Observer puis l'appel de méthode simple peut être effectué lorsque l'événement de notification est traité.

Alors ... J'ai une carte associant des threads et des files d'attente qui sont mises à jour lorsque des threads et des files d'attente sont construits et détruits. Cette carte elle-même utilise un mutex pour protéger l'accès multi-threadé.

La carte est un singleton.

J'ai été coupable d'utiliser des singletons dans le passé, car "il n'y en aura qu'un dans cette application" et croyez-moi - j'ai payé ma pénitence!

Une partie de moi ne peut s'empêcher de penser qu'il n'y aura qu'une seule carte de file d'attente / thread dans une application. L'autre voix dit que les singletons ne sont pas bons et que vous devriez les éviter.

J'aime l'idée d'éliminer le singleton et de pouvoir vous faire monter pour mes tests de l'unité. Le problème est que j'ai du mal à essayer de penser à une bonne solution alternative.

La solution "habituelle" qui a travaillé dans le passé consiste à transmettre un pointeur sur l'objet à utiliser au lieu de référencer le singleton. Je pense que cela serait délicat dans ce cas, car les observateurs et les sujets sont 10-a-penny dans ma demande et il serait très difficile de devoir passer un objet de la cartographie file / filetage dans le constructeur de chaque observateur.

Ce que j'apprécie, c'est que je pourrai avoir une seule carte dans ma demande, mais cela ne devrait pas être dans les entrailles du sujet et du code de classe d'observateur où cette décision est prise.

Peut-être que c'est un singleton valide, mais j'apprécierais également toutes les idées sur la façon dont je pourrais l'enlever.

merci.

ps. J'ai lu Qu'est-ce qui est alternatif à Singleton et Cet article mentionné dans la réponse acceptée. Je ne peux pas m'empêcher de penser que l'application apportée par un autre nom de Singleton par un autre nom. Je ne vois vraiment pas l'avantage.


8 commentaires

Pourquoi souhaitez-vous éviter des singletons? Ils ont très certainement leur place. Chaque idiome peut être mal utilisé et abusé. Mais une carte à l'échelle de la demande de fil-> notification_queue me semble raisonnable.


@Mordachai: Je comprends les singletons ont leur place et il se peut que cette carte de file d'attente / filetage soit parfaitement valide. Cela n'a commencé à me boguer quand j'écrivais des tests unitaires et je me sentais juste maladroit d'avoir le singleton là-bas.


Quelle bibliothèque de thread utilisez-vous?


@outis: nous avons notre propre API de fil


Vos threads stockent-ils des données spécifiques au fil accessibles à d'autres threads? Comme Nsthread's -Threaddictionary ?


@MORDACCHAI: Une plaine mondiale travaillerait très bien. Ce n'est pas obligé d'être un singleton. Et encore mieux, la carte pourrait être transmise à chaque fil au démarrage. Je ne me souviens peut-être jamais de voir un cas où les singletons "ont leur place".


@outis: Il s'agit d'une application Win32, l'équivalent de la fenêtre est le stockage local du fil. C'est une idée intéressante - laissez-la au système d'exploitation pour effectuer la mappage de fil / file d'attente via TLS. Tant que je peux dire «obtenir la file d'attente pour le fil x» n'est pas nécessairement juste le fil actuel.


Les gens disent principalement à utiliser des injections de dépendance ou créez l'objet une fois et transmettez-le en tant que variables de membre.


6 Réponses :


0
votes

Vos observateurs peuvent être bon marché, mais ils dépendent de la carte de filetage de la file d'attente de notification, non?

Qu'est-ce qui est gênant de faire en sorte que la dépendance explicite et en prenant le contrôle?

En ce qui concerne l'usine d'application Miško Hony décrit dans son article, les principaux avantages sont les suivants: 1) L'approche d'usine ne masque pas les dépendances et 2) les instances simples que vous dépendez de ne pas être disponibles globalement telles que tout autre objet puisse se mêler à leur état. Ainsi, en utilisant cette approche, dans tout contexte d'application haut de gamme, vous savez exactement ce qui utilise votre carte. Avec un singleton accessible à l'échelle mondiale, toute classe que vous utilisez peut faire des choses peu recommandables avec ou vers la carte.


4 commentaires

L'intention était (est) que le mécanisme de file d'attente / thread est transparent pour les utilisateurs d'observateurs - tout un observateur dérivé doit savoir que la méthode notifiée () n'est pas nécessairement un appel de méthode synchrone si le threading est impliqué. La classe de base d'observateur peut bien dépendre de la carte et d'une file d'attente de notification, mais je ne veux pas (si je peux l'aider) faites glisser cette dépendance dans des classes dérivées et forcer chaque observateur dérivé à connaître la file d'attente / la carte de fil. Il y a beaucoup de (> 500) d'observateurs dans mon application! (Je suppose que c'est ce que vous entendez par "en prendre le contrôle"?


En effet, c'est ce que je voulais dire! Je ne veux pas porter votre patience ou prêcher à la chorale ici, car vous avez déjà indiqué que vous n'aimez pas les singletons non plus - mais la transparence des singletons n'est que illusoire. C'est-à-dire que ce n'est transparent que jusqu'à ce qu'il échoue. Cela dit, l'idée peut-être que JS Bangs est plus agréable si vous avez 500 classes d'observateurs dérivés (Yikes).


@Jeff: Ce n'est pas nécessairement plus de 500 observateurs par sujet :-) Il existe de nombreux observateurs et de nombreux sujets, à divers degrés de nombreuses relations à plusieurs.


Assez juste - serait-il plus gérable si vous centralisez la création d'observateurs dans une usine? (Si vous ne le faites pas déjà! :))



1
votes

Certaines pensées vers une solution:

Pourquoi avez-vous besoin d'en faisez des notifications pour les observateurs créés sur un fil différent? Mon design préféré serait de disposer du sujet juste en informer les observateurs directement, et mettez l'ONUS sur les observateurs à se mettre en oeuvre en toute sécurité, avec la connaissance que notifié () pourrait être appelé à tout moment d'un autre fil. Les observateurs savent quelles parties de leur état doivent être protégées avec des verrous et peuvent gérer cela mieux que le sujet ou la queue . .

En supposant que vous avez vraiment une bonne raison de garder la queue , pourquoi ne pas en faire une instance? Juste faire la queue = nouvelle file d'attente () quelque part dans principal , puis transmettez cette référence. Il ne peut y avoir que tous, mais vous pouvez toujours traiter cela comme une instance et non une statique globale.


3 commentaires

Personnellement, lorsque j'écris des observateurs multithreaux, je préfère que mes méthodes d'observateurs soient toujours appelées dans le contexte de leur propre thread. Avoir un autre fil injecta son injectant seulement utile pour les cas où l'activité d'obésération est très, très lite.


@ JS BANGS: Je peux voir ce que vous dites sur le thread-Safety, mais la mise en œuvre actuelle consiste à faire face aux mutiles et à la sécurité thread-sécurité plutôt que les observateurs. Changer cela maintenant ne tomberait pas trop bien avec l'équipe! (Incidemment, c'est la carte de filetage de la file d'attente qui est le singleton; pas une file d'attente - il existe de nombreuses files d'attente et de threads; une carte). Ce qui m'inquiéterait, ce sont les changements nécessaires pour avoir à transmettre cette référence à l'objet de la carte à chaque instance d'observateur unique.


@MORDACAHAI: Oui, c'est l'approche que j'ai prise - la méthode notifiée de chaque observateur fonctionne dans le contexte de son propre fil. Il existe des mécanismes permettant de forcer une notification asynchrone (mise en file d'attente) (dans le boîtier unique) ou forcer une notification synchrone (appel de la méthode) (dans le boîtier multi-thread), mais elles sont rarement utilisées et sont destinées à une utilisation «avancée» si vous êtes conscient des conséquences.



1
votes

Qu'est-ce qui ne va pas dans la mise en place de la file d'attente dans la classe de sujet? De quoi avez-vous besoin de la carte?

Vous avez déjà une lecture de fil de la carte de la file d'attente Singleton. Au lieu de cela, faites simplement la carte à l'intérieur de la classe d'objet et fournissez deux méthodes pour souscrire un observateur: xxx

maintenant tout thread peut appeler la méthode getNext pour commencer à expédier ... bien sûr, il est tout trop simplifié, mais c'est juste l'idée.

Note: Je travaille avec l'hypothèse que vous avez déjà une architecture autour de ce modèle pour que vous ayez déjà avoir un groupe d'observateurs, un ou plusieurs sujets et que les threads vont déjà sur la carte pour faire les notifications. Cela se débarrasse du singleton mais je vous suggère de notifier du même fil et de laisser les observateurs gérer les problèmes de concurrence.


1 commentaires

Il y a une file d'attente par fil. Il peut y avoir beaucoup d'observateurs construits dans un fil. De plus, de nombreux sujets peuvent notifier du même thread, de sorte que la file d'attente par sujet est surchargée.



0
votes

Mon approche devait que les observateurs fournissent une file d'attente lorsqu'ils sont enregistrés au sujet de la question; Le propriétaire de l'observateur serait responsable de la file d'attente et de la file d'attente associée, et le sujet associerait l'observateur avec la file d'attente, sans avoir besoin d'un registre central.

Les observateurs de fil-sécurité peuvent s'inscrire sans une file d'attente et être appelés directement par le sujet.


2 commentaires

Je comprends ce que tu veux dire, mais je pense que cela expose trop de dépendances. L'un des avantages de cacher le comportement de la file d'attente provenant d'observateurs et de sujets dérivés est qu'il devient très facile de déplacer des observateurs entre les threads (dans le code source; pas d'exécution) et leur faire "automatiquement" savoir quelle est la file d'attente à utiliser; Les utilisateurs des observateurs et des sujets n'ont pas à s'inquiéter de ce détail de mise en œuvre.


Dans ce cas, un singleton est probablement ce que vous voulez. Il n'y a pas de problème conceptuel avec cela, depuis par définition, il n'y a qu'un seul "ensemble de tous les threads dans le processus". Alternativement (mais moins de manière significative), vous pouvez mettre la file d'attente dans le stockage de fil-local si votre plate-forme a une telle chose.



3
votes

Si le seul but d'essayer de vous débarrasser du singleton provient d'un point de vue du test unitaire, remplacer peut-être le singleton getter avec quelque chose que vous pouvez échanger dans un talon. XXX

ALORS Dans votre test, écrivez simplement la mise en œuvre de la carte de file d'attente / du fil. À tout le moins, cela expose un peu plus le singleton.


1 commentaires

Ah ... intéressant. En fait, j'ai déjà scindé d'autres singletons de cette manière pour les tests afin que je puisse simplement réutiliser la partie de base localement et ignorer la partie singleton, mais je ne suis pas allé aussi loin l'ajout du SetInstance méthode. Merci.



0
votes

Qu'en est-il de l'ajout d'une méthode de réinitialisation qui renvoie un singleton à son état initial que vous pouvez appeler entre tests? Cela pourrait être plus simple qu'un talon. Il pourrait être possible d'ajouter une méthode de réinitialisation générique à un modèle singleton (supprime le singleton interne et réinitialise le pointeur). Cela pourrait même inclure un registre de tous les singletons avec une méthode Master Resetall pour les réinitialiser tous!


1 commentaires

C'est quelque chose qui vaut la peine d'être réfléchi. Je pourrais combiner cela avec l'idée "plug-in" singleton de Snazzer. À votre santé.