Помогите мне удалить Синглтон: поиск альтернативы
Справочная информация. У меня есть несколько классов, реализующих шаблон дизайна объекта/наблюдателя, который я создал для потокобезопасности. 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 всех из них!