Проблемы с реализацией паттерна "Наблюдатель" - PullRequest
32 голосов
/ 19 июня 2009

Я столкнулся с интересной проблемой при реализации шаблона Observer с C ++ и STL. Рассмотрим этот классический пример:

class Observer {
public:
   virtual void notify() = 0;
};

class Subject {
public:
   void addObserver( Observer* );
   void remObserver( Observer* );
private:
   void notifyAll();
};

void Subject::notifyAll() {
   for (all registered observers) { observer->notify(); }
}

Этот пример можно найти в каждой книге по шаблонам проектирования. К сожалению, реальные системы более сложны, поэтому возникает первая проблема: некоторые наблюдатели принимают решение добавить других наблюдателей в Субъект после получения уведомления. Это делает недействительным цикл for и все итераторы, которые я использую. Решение довольно простое - я делаю снимок списка зарегистрированных наблюдателей и перебираю снимок. Добавление новых наблюдателей не делает снимок недействительным, поэтому все выглядит хорошо. Но тут возникает другая проблема: наблюдатели решают уничтожить себя, получив уведомление. Хуже того, один наблюдатель может решить уничтожить всех остальных наблюдателей (они управляются из сценариев), и это делает недействительной очередь и снимок. Я ловлю себя на переборе нераспределенных указателей.

Мой вопрос: как мне справляться с ситуациями, когда наблюдатели убивают друг друга? Есть ли готовые шаблоны? Я всегда думал, что «Обозреватель» - это самый простой шаблон проектирования в мире, но сейчас кажется, что его не так просто реализовать правильно ...

Спасибо всем за проявленный интерес. Позвольте нам иметь резюме решений:

[1] «Не делай этого» * ​​1013 * Извините, но это необходимо. Наблюдатели управляются из сценариев и собирают мусор. Я не могу контролировать сборку мусора, чтобы предотвратить их удаление;

[2] «Использовать надстройку :: сигнал» Самое многообещающее решение, но я не могу дать надежду проекту, такие решения должны приниматься только руководителем проекта (мы пишем в Playstation) ;

[3] «Использовать shared__ptr» Это предотвратит перераспределение наблюдателей. Некоторые подсистемы могут полагаться на очистку пула памяти, поэтому я не думаю, что могу использовать shared_ptr.

[4] «Отложить освобождение наблюдателя» Поставить в очередь наблюдателей для удаления во время уведомления, а затем использовать второй цикл для их удаления. К сожалению, я не могу предотвратить освобождение, поэтому я использую хитрость обертывания наблюдателя с каким-то «адаптером», сохраняя фактически список «адаптеров». На деструкторе наблюдатели отменяют назначение со своих адаптеров, затем я беру второй цикл, чтобы уничтожить пустые адаптеры.

p.s. это нормально, что я редактирую свой вопрос, чтобы подвести итог всего поста? Я нуб на StackOverflow ...

Ответы [ 14 ]

0 голосов
/ 19 июня 2009

Определите и используйте сверхмощный итератор над контейнером уведомителей, который устойчив к удалению (например, обнуляет, как упоминалось ранее) и может обрабатывать добавление (например, добавление)

С другой стороны, если вы хотите принудительно хранить const контейнера во время уведомления, объявите notifyAll и контейнер, который повторяется, как const.

0 голосов
/ 19 июня 2009

Как насчет наличия члена-итератора с именем current (инициализированный как итератор end). Тогда

void remObserver(Observer* obs)
{
    list<Observer*>::iterator i = observers.find(obs);
    if (i == current) { ++current; }
    observers.erase(i);
}

void notifyAll()
{
    current = observers.begin();
    while (current != observers.end())
    {
        // it's important that current is incremented before notify is called
        Observer* obs = *current++;
        obs->notify(); 
    }
}
0 голосов
/ 19 июня 2009

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

В любом случае, из вашего описания кажется, что проблема не в параллелизме (multi-thrading), а в мутациях, вызванных вызовом Observer :: notify (). Если это так, то вы можете решить проблему, используя вектор и обходя его с помощью индекса, а не итератора.

for(int i = 0; i < observers.size(); ++i)
  observers[i]->notify();
0 голосов
/ 19 июня 2009

Как насчет использования связанного списка в цикле for?

...