Помогите мне удалить Синглтон: поиск альтернативы

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

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

Карта является одноэлементной.

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

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

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

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

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

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

Спасибо.

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

Ответ 1

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

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
}

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

Ответ 2

Некоторые мысли к решению:

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

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

Ответ 3

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

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

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 для начала диспетчеризации... конечно, все это слишком упрощено, но это просто идея.

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

Ответ 4

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

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

Что касается приложения factory Miško Hevery описывает в своей статье, то самые большие преимущества заключаются в том, что 1) подход factory не скрывает зависимости и 2) отдельные экземпляры, на которые вы зависите, недоступны в глобальном масштабе, так что любой другой объект может вмешиваться со своим состоянием. Поэтому, используя этот подход, в любом конкретном контексте приложения верхнего уровня вы точно знаете, что используете вашу карту. Используя глобально доступный синглтон, любой класс, который вы используете, может делать неприятные вещи с или на карте.

Ответ 5

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

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

Ответ 6

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