在我的应用程序中,我有很多课程。这些类中的大多数都存储了一些数据,如果其中一个数据类的内容发生变化,我的应用程序中的其他模块也会“更新”,这一点非常重要。
这样做的典型方法是:
void MyDataClass::setMember(double d)
{
m_member = d;
notifyAllObservers();
}
如果不经常更改成员并且“观察类”需要尽可能快地更新,这是一个非常好的方法。
另一种观察变化的方法是:
void MyDataClass::setMember(double d)
{
setDirty();
m_member = d;
}
如果成员多次更改,这是一个很好的方法,并且“观察类”会在所有“脏”实例中定期查看。
不幸的是,我的课程中混合了两种数据成员。有些是经常改变的(我可以和普通的观察者一起生活),有些被改变很多次(这是在复杂的数学算法中),并且每当值改变时调用观察者将会破坏我的应用程序的性能。
是否还有其他观察数据变化的技巧,或者您可以轻松组合几种观察数据变化的不同方法的模式?
虽然这是一个与语言无关的问题(我可以尝试理解其他语言中的示例),但最终的解决方案应该适用于C ++。
答案 0 :(得分:6)
你所描述的两种方法涵盖(概念上)两个方面,但我认为你没有充分解释它们的利弊。
您应该注意一个项目,这是人口因素。
如果您有许多通知者,并且您的观察者应该遍历每个通知符以发现dirty
的2或3 ...它将无效。另一方面,如果你有很多观察者,并且在每次更新时都需要通知他们所有人,那么你可能注定要失败,因为简单地遍历所有这些就会破坏你的表现。
有一种可能性,你没有谈到:将这两种方法结合起来,与另一种间接方式相结合。
GlobalObserver
GlobalObserver
但这并不容易,因为每个观察者都需要记住它最后一次检查的时间,仅通知它尚未观察到的变化。通常的技巧是使用时代。
Epoch 0 Epoch 1 Epoch 2
event1 event2 ...
... ...
每个观察者都记得它需要阅读的下一个纪元(当观察者订阅它时,它被给予当前的纪元),并从该纪元读取到当前纪元以了解所有事件。通常,通知程序无法访问当前时期,例如,您可以决定每次读取请求到达时切换时期(如果当前时期不为空)。
这里的困难是知道何时丢弃时期(当不再需要它们时)。这需要某种引用计数。请记住,GlobalObserver
是将当前时期返回给对象的那个。因此,我们为每个时代引入一个计数器,它只计算有多少观察者尚未观察到这个时代(以及后来的时期)。
也可以将它与超时结合起来,记录我们最后一次修改时期(即创建下一个时期)并决定在一定时间后我们可以丢弃它(在这种情况下我们收回计数器和将它添加到下一个时代。)
请注意,该方案可扩展为多线程,因为一个纪元可以进行写入(在堆栈上进行推送操作),而其他时间段是只读的(原子计数器除外)。在不需要分配内存的情况下,可以使用无锁操作来推送堆栈。在堆栈完成时决定切换纪元是完全合理的。
答案 1 :(得分:4)
观察数据变化的其他技巧
不是真的。你有“推”和“拉”设计模式。没有任何其他选择。
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 :(得分: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
,并且mutator不需要关心差异:
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_和pull_通知器的任意组合。如果您确定给定的mutator只会使用pull_notifier
或push_notifiers,您可以考虑使用带有通知程序的模板作为模板参数(策略)来避免虚函数调用的开销(可能忽略不计)对于push_notifier
,但pull_notifier
的情况则较少。
答案 3 :(得分:0)
您描述了两个可用的高级选项(推送与拉/轮询)。我不知道其他任何选择。