在观察者模式 (C++) 中用更新请求问题淹没视图
flooding the views with update requests issue in observer pattern(C++)
我有一些MVC代码,它使用了观察者模式,如下所示:
void Model::ChangeMethod1()
{
m_A = m_A + 1;
...
Notify();
}
void Model::ChangeMethod2()
{
m_A = m_A + 2;
...
Notify();
}
void Model::ChangeMethod3()
{
ChangeMethod1();
ChangeMethod2();
Notify();
}
void Model::ChangeMethod4()
{
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
Notify();
}
像ChangeMethodX
这样的函数有很多,都会对模型进行修改,并通知查看者,当查看者收到事件后,他们自己会refresh/update。
你看,每个函数 ChangeMethodX
都有一个 Notify() 函数,它在内部向观察者发送一个事件。
但我不希望观察者在每个函数中收到太多事件,因为会有太多事件,我希望每个顶级函数调用无论是否有任何内部函数调用都只发送一个更新事件到观众。
我认为这是一个很常见的问题,在很多情况下都会发生,例如 MVC 模式,因为模型会通知查看者进行更新。但是如果模型在顶层函数调用中多次更改,我们必须避免泛洪事件。
我想到了 2 种可能的方法:
如果主题完全在您的控制之下并且此解决方案不太具有侵入性,您可以添加一个可选参数来指定所调用的 ChangeMethodX
是否是顶级函数,如下所示:
void Model::ChangeMethod1(bool topLevel = true)
{
m_A = m_A + 1;
...
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod2(bool topLevel = true)
{
m_A = m_A + 2;
...
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod3(bool topLevel = true)
{
ChangeMethod1(false);
ChangeMethod2(false);
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod4(bool topLevel = true)
{
ChangeMethod1(false);
ChangeMethod2(false);
ChangeMethod3(false);
NotifyIfTopLevel(topLevel);
}
void Model::NotifyIfTopLevel(bool topLevel)
{
if (topLevel)
Notify();
}
但是,它在大多数情况下都很丑陋,并且可能会弄脏您的界面。
另一方面,如果您必须处理并发,则您可以选择的第二种方法存在风险。此外,如果您捕获异常并处理它,您必须记住将对象恢复到正确的状态(is_changing--
如果尚未调用),否则观察者将不会再收到通知。
int is_changing = 0;
void Model::ChangeMethod1()
{
m_A = m_A + 1;
...
NotifyIfNotChanging();
}
void Model::ChangeMethod2()
{
m_A = m_A + 2;
...
NotifyIfNotChanging();
}
void Model::ChangeMethod3()
{
is_changing++;
ChangeMethod1();
ChangeMethod2();
is_changing--;
NotifyIfNotChanging();
}
void Model::ChangeMethod4()
{
is_changing++;
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
is_changing--;
NotifyIfNotChanging();
}
void Model::NotifyIfNotChanging()
{
if (is_changing == 0)
Notify();
}
如果你有那么多 ChangeMethodX
方法,可以考虑使用面向方面的框架来分离通知观察者的关注点。特别是如果你需要重复 is_changing++
/--
或简单的 Notify
调用,将它们移动到适当的方面 class 肯定会更具可读性。
编辑
至于 RAII 方法,在我看来它有点被过度使用了,因为您没有资源可以释放,每次创建和处理对象都对您的需求来说太过分了。
顺便说一下,如果你想走这条路,那么我建议你修复一些代码味道。
- 您没有正确封装
SetTopLevelCall
。它不应该是 public
因为你的 class 的用户不能乱用它。
- 有一个新的 public class
DeferredEventSender
与您的 Model
class 紧密耦合。最糟糕的部分是它负责 Notify
方法,该方法应该由 Model
本身调用。此外,您排除了需要访问 Model
私有字段和函数的可能性。
下面是我将如何面对这些问题,尽管它还不完美。
class Model
{
public:
Model()
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
protected:
class DeferredEventSender
{
public:
DeferredEventSender(Model* m)
{
_m = m;
doCallNotify = _m->topLevel;
_m->topLevel = false;
}
~DeferredEventSender()
{
if (doCallNotify)
{
_m->Notify();
_m->topLevel = true;
}
}
Model* _m;
bool doCallNotify;
};
bool topLevel = true;
int m_A;
int m_B;
};
void Model::ChangeMethod1()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 1;
}
...
我只是按照 Marco Luzzara 的第二种方法,创建了一个简单的演示 C++ 代码,见下文:
修订版 1:
#include <iostream>
using namespace std;
class Model
{
public:
Model()
: m_TopLevelCallScope(false)
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
bool IsTopLevelCall()
{
return m_TopLevelCallScope;
}
void SetTopLevelCall(bool topLevel)
{
m_TopLevelCallScope = topLevel;
}
private:
// if this variable is true, it means a top level call scope is entered
// then all the inner call should not send event, the final event could
// send when the top level sender get destructed
bool m_TopLevelCallScope;
// other members
int m_A;
int m_B;
};
// this is a deferred notification
// each function should create a local object
// but only the top level object can finally send a notification
class DeferredEventSender
{
public:
DeferredEventSender(Model* model)
: m_Model(model)
{
if(m_Model->IsTopLevelCall() == false)
{
m_Model->SetTopLevelCall(true);
m_TopLevelCallScope = true;
}
else
{
m_TopLevelCallScope = false;
}
}
~DeferredEventSender()
{
if (m_TopLevelCallScope == true)
{
// we are exiting the top level call, so restore it to false
// it's time to send the notification now
m_Model->SetTopLevelCall(false);
m_Model->Notify();
}
// do nothing if m_TopLevelCallScope == false
// because this means we are in a inner function call
}
bool m_TopLevelCallScope;
Model* m_Model;
};
void Model::ChangeMethod1()
{
DeferredEventSender sender(this);
m_A = m_A + 1;
}
void Model::ChangeMethod2()
{
DeferredEventSender sender(this);
m_A = m_A + 2;
}
void Model::ChangeMethod3()
{
DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
}
void Model::ChangeMethod4()
{
DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
}
void Model::Notify()
{
cout << "Send event!" << endl;
}
int main()
{
Model m;
m.ChangeMethod1();
m.ChangeMethod2();
m.ChangeMethod3();
m.ChangeMethod4();
return 0;
}
下面是演示 C++ 代码的输出:
Send event!
Send event!
Send event!
Send event!
你看在main()函数中,我只有4个函数调用,而且只有4个事件被发送。
我使用的方法是在每个ChangeMethodX
方法中放一个DeferredEventSender
局部对象,如果是顶层函数调用,这个对象会有它的成员变量m_TopLevelCallScope
设置为true,如果是内部函数调用,m_TopLevelCallScope
设置为false。
当DeferredEventSender
局部对象离开作用域时,会检查是否是顶级对象,如果为真,则发送事件,所以所有内部函数调用都不会发送事件。
可以扩展demo代码,让事件在DeferredEventSender
对象或Model
对象中的一个std::queue<Event>
中累积存储,当top DeferredEventSender
对象被销毁,我们可以 运行 std::queue<Event>
中的过滤器,并删除重复的事件,并发送我们实际需要的事件。
根据 Marco Luzzara 的建议,这是修改后的版本,谢谢 Marco Luzzara!
修订版 2:
#include <iostream>
using namespace std;
class Model
{
public:
Model()
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
protected:
class DeferredEventSender
{
public:
DeferredEventSender(Model* m)
{
m_Model = m;
// the first instance of the DeferredEventSender will copy the status of m_TopLevel
// and all the later(inner) instances will have false m_TopLevel
m_DoCallNotify = m_Model->m_TopLevel;
m_Model->m_TopLevel = false;
}
~DeferredEventSender()
{
// we only call Notify on the top level DeferredEventSender
if (m_DoCallNotify)
{
m_Model->Notify();
m_Model->m_TopLevel = true;
}
}
Model* m_Model;
bool m_DoCallNotify;
};
bool m_TopLevel = true;
int m_A;
int m_B;
};
void Model::ChangeMethod1()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 1;
}
void Model::ChangeMethod2()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 2;
}
void Model::ChangeMethod3()
{
Model::DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
}
void Model::ChangeMethod4()
{
Model::DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
}
void Model::Notify()
{
cout << "Send event!" << endl;
}
int main()
{
Model m;
m.ChangeMethod1();
m.ChangeMethod2();
m.ChangeMethod3();
m.ChangeMethod4();
return 0;
}
我有一些MVC代码,它使用了观察者模式,如下所示:
void Model::ChangeMethod1()
{
m_A = m_A + 1;
...
Notify();
}
void Model::ChangeMethod2()
{
m_A = m_A + 2;
...
Notify();
}
void Model::ChangeMethod3()
{
ChangeMethod1();
ChangeMethod2();
Notify();
}
void Model::ChangeMethod4()
{
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
Notify();
}
像ChangeMethodX
这样的函数有很多,都会对模型进行修改,并通知查看者,当查看者收到事件后,他们自己会refresh/update。
你看,每个函数 ChangeMethodX
都有一个 Notify() 函数,它在内部向观察者发送一个事件。
但我不希望观察者在每个函数中收到太多事件,因为会有太多事件,我希望每个顶级函数调用无论是否有任何内部函数调用都只发送一个更新事件到观众。
我认为这是一个很常见的问题,在很多情况下都会发生,例如 MVC 模式,因为模型会通知查看者进行更新。但是如果模型在顶层函数调用中多次更改,我们必须避免泛洪事件。
我想到了 2 种可能的方法:
如果主题完全在您的控制之下并且此解决方案不太具有侵入性,您可以添加一个可选参数来指定所调用的 ChangeMethodX
是否是顶级函数,如下所示:
void Model::ChangeMethod1(bool topLevel = true)
{
m_A = m_A + 1;
...
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod2(bool topLevel = true)
{
m_A = m_A + 2;
...
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod3(bool topLevel = true)
{
ChangeMethod1(false);
ChangeMethod2(false);
NotifyIfTopLevel(topLevel);
}
void Model::ChangeMethod4(bool topLevel = true)
{
ChangeMethod1(false);
ChangeMethod2(false);
ChangeMethod3(false);
NotifyIfTopLevel(topLevel);
}
void Model::NotifyIfTopLevel(bool topLevel)
{
if (topLevel)
Notify();
}
但是,它在大多数情况下都很丑陋,并且可能会弄脏您的界面。
另一方面,如果您必须处理并发,则您可以选择的第二种方法存在风险。此外,如果您捕获异常并处理它,您必须记住将对象恢复到正确的状态(is_changing--
如果尚未调用),否则观察者将不会再收到通知。
int is_changing = 0;
void Model::ChangeMethod1()
{
m_A = m_A + 1;
...
NotifyIfNotChanging();
}
void Model::ChangeMethod2()
{
m_A = m_A + 2;
...
NotifyIfNotChanging();
}
void Model::ChangeMethod3()
{
is_changing++;
ChangeMethod1();
ChangeMethod2();
is_changing--;
NotifyIfNotChanging();
}
void Model::ChangeMethod4()
{
is_changing++;
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
is_changing--;
NotifyIfNotChanging();
}
void Model::NotifyIfNotChanging()
{
if (is_changing == 0)
Notify();
}
如果你有那么多 ChangeMethodX
方法,可以考虑使用面向方面的框架来分离通知观察者的关注点。特别是如果你需要重复 is_changing++
/--
或简单的 Notify
调用,将它们移动到适当的方面 class 肯定会更具可读性。
编辑
至于 RAII 方法,在我看来它有点被过度使用了,因为您没有资源可以释放,每次创建和处理对象都对您的需求来说太过分了。 顺便说一下,如果你想走这条路,那么我建议你修复一些代码味道。
- 您没有正确封装
SetTopLevelCall
。它不应该是public
因为你的 class 的用户不能乱用它。 - 有一个新的 public class
DeferredEventSender
与您的Model
class 紧密耦合。最糟糕的部分是它负责Notify
方法,该方法应该由Model
本身调用。此外,您排除了需要访问Model
私有字段和函数的可能性。
下面是我将如何面对这些问题,尽管它还不完美。
class Model
{
public:
Model()
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
protected:
class DeferredEventSender
{
public:
DeferredEventSender(Model* m)
{
_m = m;
doCallNotify = _m->topLevel;
_m->topLevel = false;
}
~DeferredEventSender()
{
if (doCallNotify)
{
_m->Notify();
_m->topLevel = true;
}
}
Model* _m;
bool doCallNotify;
};
bool topLevel = true;
int m_A;
int m_B;
};
void Model::ChangeMethod1()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 1;
}
...
我只是按照 Marco Luzzara 的第二种方法,创建了一个简单的演示 C++ 代码,见下文:
修订版 1:
#include <iostream>
using namespace std;
class Model
{
public:
Model()
: m_TopLevelCallScope(false)
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
bool IsTopLevelCall()
{
return m_TopLevelCallScope;
}
void SetTopLevelCall(bool topLevel)
{
m_TopLevelCallScope = topLevel;
}
private:
// if this variable is true, it means a top level call scope is entered
// then all the inner call should not send event, the final event could
// send when the top level sender get destructed
bool m_TopLevelCallScope;
// other members
int m_A;
int m_B;
};
// this is a deferred notification
// each function should create a local object
// but only the top level object can finally send a notification
class DeferredEventSender
{
public:
DeferredEventSender(Model* model)
: m_Model(model)
{
if(m_Model->IsTopLevelCall() == false)
{
m_Model->SetTopLevelCall(true);
m_TopLevelCallScope = true;
}
else
{
m_TopLevelCallScope = false;
}
}
~DeferredEventSender()
{
if (m_TopLevelCallScope == true)
{
// we are exiting the top level call, so restore it to false
// it's time to send the notification now
m_Model->SetTopLevelCall(false);
m_Model->Notify();
}
// do nothing if m_TopLevelCallScope == false
// because this means we are in a inner function call
}
bool m_TopLevelCallScope;
Model* m_Model;
};
void Model::ChangeMethod1()
{
DeferredEventSender sender(this);
m_A = m_A + 1;
}
void Model::ChangeMethod2()
{
DeferredEventSender sender(this);
m_A = m_A + 2;
}
void Model::ChangeMethod3()
{
DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
}
void Model::ChangeMethod4()
{
DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
}
void Model::Notify()
{
cout << "Send event!" << endl;
}
int main()
{
Model m;
m.ChangeMethod1();
m.ChangeMethod2();
m.ChangeMethod3();
m.ChangeMethod4();
return 0;
}
下面是演示 C++ 代码的输出:
Send event!
Send event!
Send event!
Send event!
你看在main()函数中,我只有4个函数调用,而且只有4个事件被发送。
我使用的方法是在每个ChangeMethodX
方法中放一个DeferredEventSender
局部对象,如果是顶层函数调用,这个对象会有它的成员变量m_TopLevelCallScope
设置为true,如果是内部函数调用,m_TopLevelCallScope
设置为false。
当DeferredEventSender
局部对象离开作用域时,会检查是否是顶级对象,如果为真,则发送事件,所以所有内部函数调用都不会发送事件。
可以扩展demo代码,让事件在DeferredEventSender
对象或Model
对象中的一个std::queue<Event>
中累积存储,当top DeferredEventSender
对象被销毁,我们可以 运行 std::queue<Event>
中的过滤器,并删除重复的事件,并发送我们实际需要的事件。
根据 Marco Luzzara 的建议,这是修改后的版本,谢谢 Marco Luzzara!
修订版 2:
#include <iostream>
using namespace std;
class Model
{
public:
Model()
{
}
~Model()
{
}
void ChangeMethod1();
void ChangeMethod2();
void ChangeMethod3();
void ChangeMethod4();
void Notify();
protected:
class DeferredEventSender
{
public:
DeferredEventSender(Model* m)
{
m_Model = m;
// the first instance of the DeferredEventSender will copy the status of m_TopLevel
// and all the later(inner) instances will have false m_TopLevel
m_DoCallNotify = m_Model->m_TopLevel;
m_Model->m_TopLevel = false;
}
~DeferredEventSender()
{
// we only call Notify on the top level DeferredEventSender
if (m_DoCallNotify)
{
m_Model->Notify();
m_Model->m_TopLevel = true;
}
}
Model* m_Model;
bool m_DoCallNotify;
};
bool m_TopLevel = true;
int m_A;
int m_B;
};
void Model::ChangeMethod1()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 1;
}
void Model::ChangeMethod2()
{
Model::DeferredEventSender sender(this);
m_A = m_A + 2;
}
void Model::ChangeMethod3()
{
Model::DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
}
void Model::ChangeMethod4()
{
Model::DeferredEventSender sender(this);
ChangeMethod1();
ChangeMethod2();
ChangeMethod3();
}
void Model::Notify()
{
cout << "Send event!" << endl;
}
int main()
{
Model m;
m.ChangeMethod1();
m.ChangeMethod2();
m.ChangeMethod3();
m.ChangeMethod4();
return 0;
}