C++ 观察数据变化的不同方式
在我的应用程序中,我有很多类。这些类中的大多数都存储了相当多的数据,如果其中一个数据类的内容发生了变化,那么我的应用程序中的其他模块也会被“更新”,这一点很重要 这样做的典型方式如下:C++ 观察数据变化的不同方式,c++,language-agnostic,design-patterns,observer-pattern,C++,Language Agnostic,Design Patterns,Observer Pattern,在我的应用程序中,我有很多类。这些类中的大多数都存储了相当多的数据,如果其中一个数据类的内容发生了变化,那么我的应用程序中的其他模块也会被“更新”,这一点很重要 这样做的典型方式如下: void MyDataClass::setMember(double d) { m_member = d; notifyAllObservers(); } 如果成员不经常更换,并且“观察类”需要尽快更新,那么这是一个非常好的方法 观察变化的另一种方式是: void MyDataClass::setMember(
void MyDataClass::setMember(double d)
{
m_member = d;
notifyAllObservers();
}
如果成员不经常更换,并且“观察类”需要尽快更新,那么这是一个非常好的方法
观察变化的另一种方式是:
void MyDataClass::setMember(double d)
{
setDirty();
m_member = d;
}
如果成员被多次更改,并且“观察类”在所有“脏”实例中定期查看,那么这是一个很好的方法
不幸的是,我的类中混合了这两种数据成员。有些变化不太频繁(我可以和普通的观察者一起生活),有些变化很多次(这是在复杂的数学算法中),每次值变化都调用观察者会破坏我的应用程序的性能
是否还有其他观察数据更改的技巧,或者可以轻松组合几种不同的观察数据更改方法的模式
虽然这是一个相当独立的语言问题(我可以尝试理解其他语言的例子),但最终的解决方案应该在C++中工作。
< p>你描述了两个可用的高级选项(推VS拉/轮询)。我知道没有其他选择 观察数据变化的其他技巧 不是真的。你有“推”和“拉”的设计模式。没有其他选择notifyAllobserver
是一个推式操作,普通属性访问是一个拉式操作
我建议保持一致。显然,您会遇到这样的情况:一个对象有许多更改,但所有更改不会渗透到其他对象
别被这搞糊涂了
观测者不需要仅仅因为接到变更通知就进行昂贵的计算
我认为您应该有一些这样的类来处理“频繁更改但缓慢请求”类
你有拉和推通知。我会考虑尽可能隐藏细节,所以至少通知者不需要关心差异:
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(); }
};
class MyDataClass {
notifier ¬ify;
double m_member;
public:
MyDataClass(notifier &n) : n_(n) {}
void SetMember(double d) {
m_member = d;
notify();
}
};
然后,观察者可以通过一个push\u通知器
或一个pull\u通知器
,只要它认为合适,而变异者不需要关心差异:
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(); }
};
class MyDataClass {
notifier ¬ify;
double m_member;
public:
MyDataClass(notifier &n) : n_(n) {}
void SetMember(double d) {
m_member = d;
notify();
}
};
目前,我在编写它时,每个mutator只有一个观察者,但是如果您需要更多,将其更改为指向给定mutator的观察者对象的指针向量是相当简单的。这样,一个给定的mutator将支持push_uuu和pull_uuuu通知程序的任意组合。如果你确信一个给定的调制器只会使用<代码> Pull NOTIVER < /Cult> s或Purth-NosiTube,你可以考虑使用一个带有通知器的模板作为一个模板参数(一个策略)来避免虚函数调用的开销(对于一个代码> PurthoNoistor 可能是可以忽略的,但是对于<代码> Pull通知器更少。.您描述的两种方法(从概念上)涵盖了这两个方面,但是我认为您没有充分解释它们的优缺点 有一点你应该注意,那就是人口因素
- 当有很多通知程序和很少的观察者时,Push方法非常有用
- 当通知程序和观察程序较少时,Pull方法非常有用
脏的2或3个通知程序
。。。这行不通。另一方面,如果您有许多观察者,并且在每次更新时您都需要通知所有观察者,那么您很可能注定要失败,因为简单地遍历所有观察者会降低您的性能
但是,有一种可能性您没有提到:将这两种方法结合起来,再进行另一种间接处理
- 将每次更改推送到
GlobalObserver
- 必要时,让每个观察者检查
GlobalObserver
Epoch 0 Epoch 1 Epoch 2
event1 event2 ...
... ...
每个观察者都会记住它需要读取的下一个历元(当观察者订阅时,它会得到当前历元作为回报),并从这个历元一直读取到当前历元,以了解所有事件。通常,通知程序无法访问当前历元,例如,您可以决定在每次读取请求到达时切换历元(如果当前历元不是空的)
这里的困难是要知道什么时候该抛弃时代(当不再需要它们的时候)。这需要某种类型的引用计数。请记住,GlobalObserver
是向对象返回当前纪元的服务器。因此,我们为每个历元引入一个计数器,它只计算有多少观测者尚未观察到这个历元(以及随后的历元)
- 订阅时,我们返回历元编号并递增此历元的计数器
- 在轮询时,我们减少被轮询历元的计数器,返回当前历元编号并增加其计数器
- 取消订阅时,我们减少历元-->的计数器,确保析构函数取消订阅李>