2010-07-01 2 views
3

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

Типичный способ сделать это так:

void MyDataClass::setMember(double d) 
{ 
m_member = d; 
notifyAllObservers(); 
} 

Это довольно хороший метод, если член не часто меняла и нуждаются в «наблюдающей классов», чтобы быть уточненный как быстрый насколько это возможно.

Другой способ наблюдения изменений заключается в следующем:

void MyDataClass::setMember(double d) 
{ 
setDirty(); 
m_member = d; 
} 

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

К сожалению, у меня есть сочетание обоих типов данных в моих классах. Некоторые из них меняются не так часто (и я могу жить с нормальными наблюдателями), другие меняются много раз (это в сложных математических алгоритмах) и вызывая наблюдателей каждый раз, когда изменения ценности убьют производительность моего приложения.

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

Хотя это вопрос, не зависящий от языка (и я могу попытаться понять примеры на других языках), окончательное решение должно работать на C++.

ответ

4

Эти два метода, которые вы описали, охватывают (концептуально) оба аспекта, однако, я думаю, вы недостаточно объяснили свои плюсы и минусы.

Существует один предмет, о котором вы должны знать, это фактор популяции.

  • метод Нажмите велик, когда есть много оповещателей и несколько наблюдателей
  • метод Прицепных велики, когда есть несколько оповещателей и многие наблюдателей

Если у вас есть много оповещателей и ваш наблюдатель должен итерации по каждому из них, чтобы обнаружить 2 или 3, которые dirty ... это не сработает. С другой стороны, если у вас есть много наблюдателей, и при каждом обновлении вам нужно уведомить их обо всех, то вы, вероятно, обречены, потому что простое повторение всех из них собирается убить вашу работу.

Существует одна возможность, о которой вы не говорили, однако: объединение двух подходов с другим уровнем косвенности.

  • Нажмите каждое изменение в GlobalObserver
  • Пусть каждый чек наблюдателя для GlobalObserver при необходимости

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

Epoch 0  Epoch 1  Epoch 2 
event1  event2  ... 
...   ... 

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

Трудность здесь состоит в том, чтобы знать, когда отбрасывать эпохи (когда они больше не нужны). Для этого требуется подсчет ссылок. Помните, что GlobalObserver - это тот, который возвращает текущие эпохи в объекты. Итак, мы вводим счетчик для каждой эпохи, который просто подсчитывает, сколько наблюдателей еще не наблюдало эту эпоху (и последующие).

  • На подписавшись мы возвращаем число эпохальное и увеличиваем счетчик этой эпохи
  • На избирательном, мы уменьшаем счетчик эпохи опрашиваемого и возвращает номер текущей эпохи и увеличиваем его счетчик
  • На отпиской , мы уменьшаем счетчик эпохи -> убедитесь, что деструктор не подписывается!

Это также можно совместить это с тайм-аутом, регистрируя в последний раз мы модифицированный эпоху (т.е. создание следующего) и решив, что после определенного количества времени, мы можем отказаться от него (в этом случае мы востребовать счетчик и добавить его в следующую эпоху).

Обратите внимание, что схема масштабируется до многопоточности, поскольку одна эпоха доступна для записи (операция push в стеке), а остальные доступны только для чтения (кроме атомного счетчика). Для блокировки стека можно использовать операции блокировки при условии отсутствия необходимости выделения памяти. Совершенно здорово решить переключить эпоху, когда стек будет завершен.

+0

Впечатляющий. +1. – Patrick

+0

Новый вопрос по той же теме (автор: Patrick): http://stackoverflow.com/questions/3667317/best-way-to-keep-the-user-interface-up-to-date, где он замечает, что просто генерирование много событий может убить выступления. –

0

Вы описали два доступных уровня высокого уровня (push vs pull/pollling). Других вариантов, о которых я знаю, нет.

4

другие приемы наблюдений изменения данных

Не совсем. У вас есть модели «push» и «pull». Других вариантов нет.

notifyAllObservers является толчком, обычный доступ атрибута является тянуть.

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

Не путайте это.

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

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

class PeriodicObserver { 
    bool dirty; 
    public void notification(...) { 
     // save the changed value; do nothing more. Speed matters. 
     this.dirty= True; 
    } 
    public result getMyValue() { 
     if(this.dirty) { 
      // recompute now 
     } 
     return the value 
} 
2

У вас есть тянуть и нажимать уведомление.Я бы рассмотреть, пытаясь скрыть подробности, насколько это возможно, так, по крайней мере уведомитель не нужно заботиться о разнице:

class notifier { 
public: 
    virtual void operator()() = 0; 
}; 

class pull_notifier : public notifier { 
    bool dirty; 
public: 
    lazy_notifier() : dirty(false) {} 
    void operator()() { dirty = true; } 
    operator bool() { return dirty; } 
}; 

class push_notifier : public notifier { 
    void (*callback)(); 
public: 
    push_notifier(void (*c)()) : callback(c) {} 
    void operator()() { callback(); } 
}; 

Затем наблюдатель может проходить либо push_notifier или pull_notifier, как он видит подходит, и мутатор не нужно заботиться о разнице:

class MyDataClass { 
    notifier &notify; 
    double m_member; 
public: 
    MyDataClass(notifier &n) : n_(n) {} 
    void SetMember(double d) { 
     m_member = d; 
     notify(); 
    } 
}; 

на данный момент я написал это только один наблюдатель на мутатор, но это довольно просто изменить, что вектор указателей для просмотра объектов для данного мутатора, если вам нужно больше. При этом данный мутатор будет поддерживать произвольную комбинацию push_ и pull_-уведомлений. Если вы уверены, что данный мутатор будет использовать только pull_notifier или push_notifiers, вы можете подумать об использовании шаблона с уведомлением в качестве параметра шаблона (политики), чтобы избежать накладных расходов на вызов виртуальной функции (вероятно, незначительно для push_notifier, но меньше для pull_notifier).

Смежные вопросы