10 votes

Aidez-moi à supprimer un Singleton : cherchez une alternative

Contexte : J'ai quelques classes qui mettent en œuvre un modèle de conception sujet/observateur que j'ai rendu sûr pour les threads. A subject notifiera à son observers par un simple appel de méthode observer->Notified( this ) si le observer a été construit dans le même fil de discussion que celui de la notification. Mais si le observer a été construit dans un autre fil de discussion, la notification sera affichée dans un fichier queue qui sera traité ultérieurement par le thread qui a construit le fichier observer puis l'appel à la méthode simple peut être effectué lorsque l'événement de notification est traité.

J'ai donc une carte qui associe les threads et les files d'attente et qui est mise à jour lorsque les threads et les files d'attente sont construits et détruits. Cette carte utilise elle-même un mutex pour protéger l'accès multithread à cette carte.

La carte est un singleton.

J'ai été coupable d'utiliser des singletons dans le passé parce qu'il n'y en aurait qu'un seul 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 vraiment qu'une seule carte de file d'attente/thread dans une application. L'autre voix dit que les singletons ne sont pas bons et qu'il faut les éviter.

J'aime l'idée de supprimer le singleton et de pouvoir l'insérer dans mes tests unitaires. Le problème, c'est que j'ai du mal à trouver une bonne solution alternative.

La solution "habituelle" qui a fonctionné dans le passé est de passer un pointeur sur l'objet à utiliser au lieu de référencer le singleton. Je pense que ce serait délicat dans ce cas, puisque les observateurs et les sujets sont à 10 cents dans mon application et qu'il serait très gênant d'avoir à passer un objet file d'attente/thread map dans le constructeur de chaque observateur.

Ce que je comprends, c'est que je peux très bien n'avoir qu'une seule carte dans mon application, mais ce n'est pas dans les entrailles du code du sujet et de la classe d'observateurs que cette décision est prise.

Il s'agit peut-être d'un singleton valide, mais j'apprécierais également toute idée sur la façon dont je pourrais le supprimer.

Merci.

PS. J'ai lu Quelle est l'alternative à Singleton ? y cet article mentionné dans la réponse acceptée. Je ne peux m'empêcher de penser que l'ApplicationFactory n'est qu'un autre singleton sous un autre nom. Je ne vois vraiment pas l'avantage.

3voto

Snazzer Points 2688

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

class QueueThreadMapBase
{
   //virtual functions
};

class QeueueThreadMap : public QueueThreadMapBase
{
   //your real implementation
};

class QeueueThreadMapTestStub : public QueueThreadMapBase
{
   //your test implementation
};

static QueueThreadMapBase* pGlobalInstance = new QeueueThreadMap;

QueueThreadMapBase* getInstance()
{
   return pGlobalInstance;
}

void setInstance(QueueThreadMapBase* pNew)
{
   pGlobalInstance = pNew
}

Ensuite, dans votre test, il suffit d'intervertir l'implémentation de la file d'attente et de la carte de threads. Au moins, cela permet d'exposer un peu plus le singleton.

1voto

JSBձոգչ Points 25069

Quelques réflexions en vue d'une solution :

Pourquoi faut-il mettre en file d'attente des notifications pour des observateurs qui ont été créés dans un autre thread ? Je préférerais que la fonction subject il suffit de notifier directement les observateurs et de leur confier la responsabilité de s'implémenter à l'abri des threads, tout en sachant que Notified() peut être appelé à tout moment à partir d'un autre fil. Les observateurs savent quelles parties de leur état doivent être protégées par des verrous, et ils peuvent gérer cela mieux que la fonction subject ou le queue .

En supposant que vous ayez vraiment une bonne raison de garder la queue Pourquoi ne pas en faire une instance ? Il suffit de faire queue = new Queue() quelque part dans main et transmet ensuite cette référence. Il se peut qu'il n'y en ait qu'une seule, mais vous pouvez toujours la traiter comme une instance et non comme une statique globale.

1voto

Jorge Córdoba Points 18919

Qu'y a-t-il de mal à placer la file d'attente à l'intérieur de la classe sujet ? Pourquoi avez-vous besoin de la carte ?

Vous avez déjà un thread qui lit la carte de la file d'attente singleton. Au lieu de cela, il suffit de créer la carte à l'intérieur de la classe sujet et de fournir deux méthodes pour s'abonner à un observateur :

class Subject
{
  // Assume is threadsafe and all
  private QueueMap queue;
  void Subscribe(NotifyCallback, ThreadId)
  {
     // If it was created from another thread add to the map
     if (ThreadId != This.ThreadId)
       queue[ThreadId].Add(NotifyCallback);
  }

  public NotifyCallBack GetNext()
  {
     return queue[CallerThread.Id].Pop;
  }
}

Maintenant, n'importe quel thread peut appeler la méthode GetNext pour commencer à distribuer... bien sûr, tout cela est excessivement simplifié, mais c'est juste l'idée.

Nota: Je pars du principe que vous avez déjà une architecture autour de ce modèle, donc que vous avez déjà un certain nombre d'observateurs, un ou plusieurs sujets et que les threads vont déjà sur la carte pour faire les notifications. Cela permet de se débarrasser du singleton, mais je suggère de notifier à partir du même thread et de laisser les observateurs gérer les problèmes de concurrence.

0voto

Jeff Sternal Points 30147

Vos observateurs sont peut-être bon marché, mais ils dépendent de la carte notification-queue-thread, n'est-ce pas ?

Qu'y a-t-il de gênant à rendre cette dépendance explicite et à en prendre le contrôle ?

Quant à la fabrique d'applications que Miško Hevery décrit dans son article, ses principaux avantages sont les suivants : 1) l'approche de la fabrique ne cache pas les dépendances et 2) les instances uniques dont vous dépendez ne sont pas disponibles globalement, de sorte que n'importe quel autre objet peut intervenir dans leur état. Ainsi, avec cette approche, dans n'importe quel contexte d'application de haut niveau, vous savez exactement ce qui utilise votre carte. Avec un singleton accessible globalement, n'importe quelle classe que vous utilisez peut faire des choses peu recommandables avec ou sur la carte.

0voto

Mike Seymour Points 130519

Mon approche consistait à demander aux observateurs de fournir une file d'attente lors de leur enregistrement auprès du sujet ; le propriétaire de l'observateur serait responsable à la fois du fil de discussion et de la file d'attente associée, et le sujet associerait l'observateur à la file d'attente, sans qu'il soit nécessaire d'établir un registre central.

Les observateurs à sécurité intrinsèque pourraient s'enregistrer sans file d'attente et être appelés directement par le sujet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X