Помогите мне удалить синглтон: в поисках альтернативы - PullRequest
10 голосов
/ 13 ноября 2009

Справочная информация: у меня есть несколько классов, реализующих шаблон проектирования субъекта / наблюдателя, который я сделал потокобезопасным. subject уведомит его observers простым вызовом метода observer->Notified( this ), если observer был создан в том же потоке, что и уведомление. Но если observer был создан в другом потоке, то уведомление будет опубликовано на queue для последующей обработки потоком, который сконструировал observer, и тогда простой вызов метода может быть выполнен, когда событие уведомления обрабатывается.

Итак ... У меня есть карта, связывающая потоки и очереди, которая обновляется, когда потоки и очереди создаются и уничтожаются. Эта карта сама по себе использует мьютекс для защиты многопоточного доступа к ней.

Карта является одиночной.

В прошлом я был виновен в использовании синглетонов, потому что «в этом приложении будет только один», и, поверьте мне, я заплатил покаяние!

Одна часть меня не может не думать, что в приложении действительно будет только одна карта очередей / потоков. Другой голос говорит, что синглтоны не хороши, и вы должны избегать их.

Мне нравится идея удаления синглтона и возможности заглушить его для моих юнит-тестов. Проблема в том, что мне трудно найти хорошее альтернативное решение.

«Обычное» решение, которое работало в прошлом, состоит в том, чтобы передать указатель на объект для использования вместо ссылки на синглтон. Я думаю, что в этом случае было бы сложно, поскольку в моем приложении наблюдатели и субъекты стоят по 10 копеек, и было бы очень неудобно передавать объект карты очереди / потока в конструктор каждого отдельного наблюдателя.

Что я ценю, так это то, что у меня вполне может быть только одна карта в моем приложении, но она не должна быть в недрах кода субъекта и класса наблюдателя, где принимается это решение.

Может быть, это действительный синглтон, но я также буду признателен за любые идеи о том, как я могу удалить его.

Спасибо.

PS. Я прочитал Что является альтернативой синглтону и Эта статья упоминается в принятом ответе. Я не могу не думать, что ApplicationFactory это просто еще один синглтон под другим именем. Я действительно не вижу преимущества.

Ответы [ 6 ]

3 голосов
/ 13 ноября 2009

Если единственная цель - избавиться от синглтона - с точки зрения юнит-теста, возможно, заменить синглтон-геттер чем-то, на что вы можете заменить заглушку.

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
}

Тогда в вашем тесте просто поменяйте местами реализацию карты очередей / потоков. По крайней мере, это подвергает синглтон немного больше.

1 голос
/ 13 ноября 2009

Что не так с помещением очереди в предметный класс? Для чего вам карта?

У вас уже есть чтение потока из карты очереди синглтона. Вместо этого просто создайте карту внутри предметного класса и предоставьте два метода подписки наблюдателя:

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;
  }
}

Теперь любой поток может вызвать метод GetNext, чтобы начать диспетчеризацию ... конечно, все это чрезмерно упрощено, но это всего лишь идея.

Примечание: Я работаю с предположением, что у вас уже есть архитектура вокруг этой модели, так что у вас уже есть группа наблюдателей, один или несколько субъектов, и что потоки уже идут на карту делать уведомления. Это избавляет от синглтона, но я бы посоветовал вам уведомить из того же потока и позволить наблюдателям решать проблемы параллелизма.

1 голос
/ 13 ноября 2009

Некоторые мысли по поводу решения:

Зачем вам ставить уведомления для наблюдателей, которые были созданы в другом потоке? Я предпочел бы, чтобы subject просто уведомлял наблюдателей напрямую и возлагал бремя на наблюдателей, чтобы реализовать себя в потоке с учетом того, что Notified() может быть вызван в любое время из другого потока. Наблюдатели знают, какие части их состояния должны быть защищены замками, и они могут справиться с этим лучше, чем subject или queue.

Если у вас действительно есть веская причина сохранить queue, почему бы не сделать его экземпляром? Просто сделайте queue = new Queue() где-нибудь в main, а затем передайте эту ссылку. Может быть только один, но вы все равно можете рассматривать это как экземпляр, а не как глобальную статическую переменную.

0 голосов
/ 15 ноября 2009

Как насчет добавления метода Reset, который возвращает одиночный элемент в его начальное состояние, которое вы можете вызывать между тестами? Это может быть проще, чем заглушка. Может быть возможно добавить универсальный метод Reset к шаблону Singleton (удаляет внутренний одноэлементный pimpl и сбрасывает указатель). Это может даже включать реестр всех синглетонов с основным методом ResetAll для их сброса!

0 голосов
/ 13 ноября 2009

Мой подход состоял в том, чтобы наблюдатели предоставляли очередь, когда они регистрировались у субъекта; владелец наблюдателя будет нести ответственность как за поток, так и за связанную очередь, а субъект будет связывать наблюдателя с очередью без необходимости в центральном реестре.

Потокобезопасные наблюдатели могут регистрироваться без очереди и могут вызываться непосредственно субъектом.

0 голосов
/ 13 ноября 2009

Ваши наблюдатели могут быть дешевыми, но они зависят от карты очереди уведомлений, верно?

Что неудобного в том, чтобы сделать эту зависимость явной и взять ее под контроль?

Что касается фабрики приложений, которую Мишко Хевери описывает в своей статье, самые большие преимущества заключаются в том, что 1) фабричный подход не скрывает зависимости и 2) отдельные экземпляры, от которых вы зависите, не доступны глобально, так что любой другой объект может вмешиваться в их состояние. Таким образом, используя этот подход, в любом заданном контексте приложения верхнего уровня вы точно знаете, что использует ваша карта. При использовании глобально доступного синглтона любой используемый вами класс может совершать неприятные действия с картой или с ней.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...