我有一些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
,将对模型进行更改,并通知查看器,当查看器接收到事件时,它们将刷新/更新自己。
你看,每个函数ChangeMethodX
都有一个Notify()函数,它在内部发送一个事件给观察者。
但是我不希望观察者在每个函数中接收太多的事件,因为会有太多的事件,我希望每个顶级函数调用,无论它是否有任何内部函数调用,只发送一个更新事件给查看器。
我认为这是一个非常常见的问题,发生在许多情况下,如MVC模式,作为一个模型将通知观众得到更新。但是,如果模型在顶级函数调用中多次更改,我们必须避免泛滥事件。
我想到了两种可能的方法:
如果主题完全在您的控制之下,并且此解决方案不是太侵入,您可以添加一个可选参数,指定所调用的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
调用,将它们移动到适当的方面类中肯定会更具可读性。
编辑
至于RAII方法,在我看来,它在这里被过度使用了,因为您没有资源可以释放,每次创建和处置对象对于您的需求来说都是相当多余的。顺便说一下,如果您想遵循这条路径,那么我建议您修复一些代码异味。
- 您没有正确封装
SetTopLevelCall
。它不应该是public
,因为你的类的用户不能乱动它。 有一个新的公共类
DeferredEventSender
,它与你的Model
类紧密耦合。最糟糕的部分是它负责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
局部对象离开作用域时,它将检查它是否是顶级对象,如果为true,它将发送事件,因此所有内部函数调用都不会发送事件。
演示代码可以扩展,使事件可以累积并存储在DeferredEventSender
对象或Model
对象中的std::queue<Event>
中,当顶部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;
}