使用 CRTP 和 'anonymous types/template' 实现观察者模式
Implementing the observer pattern using CRTP and 'anonymous types/template'
我正在开发一个库来解析某个 XML 文件(使用 RapidXML)和 returns 我的一个对象,其中包含该文件数据。 XML 文件是由其他人的应用程序创建的。我需要使用观察者模式,因为速度非常重要,例如:
假设一个文件有 10.000 个标签及其子节点。在一个简单的解析器中,它们将按照它们被发现的顺序添加到 std::vector 中。然后,在文件被解析后,我们需要再次迭代这 10.000 个值,并用它们做任何我们想做的事。
通过使用观察者模式,我允许外部观察者(无论哪个 class 想要观察和通知每个获取的节点都必须继承我的库附带的 AbstractObserver,并实现他的功能)到成为解析过程的一部分,而不需要在解析的节点上再次迭代 X 次。 HOWEVER... 有多种节点,例如:tileset, layer, imagelayer 等等(根据Observer/Subject模式,其对应节点需要多个onObserved和notify函数,可能有很多'duplicated' 代码 - 注意:不使用继承。请参阅下面的 'bad' 示例)。我可以简单地让节点继承自某种 BaseNode class,但我不想在这里使用继承,因为我不想处理指针。相反,我使用枚举来键入节点,这就是我的问题所在。
/* ################## ABSTRACT OBSERVER #################### */
// Implements the observer pattern, using the CRTP pattern
template<class ConcreteObserver>
class AbstractObserver
{
public:
virtual ~AbstractObserver() { }
template<class Attribute>
inline void onObserved(Attribute attribute) {
// This requires ConcreteObserver to have a method onObservedImpl<Attribute>
static_cast<const ConcreteObserver*>(this)->onObservedImpl(attribute);
}
};
/* ################## ABSTRACT SUBJECT #################### */
class AbstractSubject
{
public:
virtual ~AbstractSubject() { }
// ???????
inline void attach(AbstractObserver<??????>* observer) {
m_observers.push_back(observer);
}
// ???????
inline void detatch(AbstractObserver<??????>* observer) {
auto& it = std::find(m_observers.begin(), m_observers.end(), observer);
// Remove the observer from the list, if it was observing
if (it != m_observers.end())
m_observers.erase(it);
}
protected:
template<typename Attribute>
void notify(Attribute& attribute) {
for (auto* observer : m_observers)
observer->onObserved(attribute)
}
private:
// ???????
std::vector<AbstractObserver<??????>*> m_observers;
};
/* ################## CONCRETE OBSERVER #################### */
class SomeConcreteObserver : public AbstractObserver<SomeConcreteObserver>
{
public:
// The CRTP 'virtual' function implementation
template<class Attribute>
void onObservedImpl(Attribute attribute)
{
// Filter the attribute and use it accordingly
switch (attribute.type)
{
// ....
}
}
};
/* ################## CONCRETE SUBJECT #################### */
class Parser : public AbstractSubject
{
public:
void parse(/* params */)
{
Foo f;
notify<Foo>(f);
// Later on....
Bar b;
notify<Bar>(b);
}
};
如我们所见,我也在使用 CRTP,因为我需要 'templated virtual functions',否则无法实现。由于 AbstractObserver 需要一个类型(因为 CRTP),我无法在 AbstractSubject class(见上文)中正确使用它们。甚至可以使用像 Java 或类似的匿名模板吗?我相信这会完成工作。
这是我想到的 'bad' 示例的实现,但这是我能针对这种情况想出的最佳示例:
// Remove the CRTP
class AbstractObserver
{
public:
virtual ~AbstractObserver() { }
virtual void onNodeA(NodeA n) = 0;
virtual void onNodeB(NodeB n) = 0;
virtual void onNodeC(NodeC n) = 0;
virtual void onNodeD(NodeD n) = 0;
virtual void onNodeE(NodeE n) = 0;
// .....
};
class AbstractSubject
{
public:
// ....
protected:
void notifyNodeA(NodeA n) {
for (auto* observer : m_observers)
observer->onNodeA(n);
}
void notifyNodeB(NodeB n) {
for (auto* observer : m_observers)
observer->NodeB(n);
}
void notifyNodeC(NodeC n) { }
void notifyNodeD(NodeD n) { }
void notifyNodeE(NodeE n) { }
// ....
private:
std::vector<Observer*> m_observers;
};
解决方案必须使用 C++11 或 bellow 并且没有提升。
解决方案 #1:非常潮湿但简单
#include <vector>
#include <algorithm>
#include <iostream>
template<typename TAttribute>
class Observer
{
public:
virtual void Observe(TAttribute& attribute) = 0;
virtual ~Observer() = default;
};
template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
void Observe(TAttribute& attribute)
{
std::cout << attribute << std::endl;
}
~OutputtingObserver() = default;
};
template<typename TAttribute>
class Subject
{
private:
std::vector<Observer<TAttribute>*> mutable m_observers;
public:
void Attach(Observer<TAttribute>& observer) const
{
m_observers.push_back(&observer);
}
void Detach(Observer<TAttribute>& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
void Notify(TAttribute& attribute)
{
for (auto observer : m_observers)
observer->Observe(attribute);
}
};
class NodeA
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
{
return o << "a";
}
};
class NodeB
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
{
return o << "b";
}
};
class Parser
{
private:
Subject<NodeA> m_subjectA;
Subject<NodeB> m_subjectB;
public:
void Parse()
{
auto a = NodeA();
auto b = NodeB();
m_subjectA.Notify(a);
m_subjectB.Notify(b);
}
void Attach(Observer<NodeA>& observer)
{
m_subjectA.Attach(observer);
}
void Attach(Observer<NodeB>& observer)
{
m_subjectB.Attach(observer);
}
};
int main()
{
auto observerA = OutputtingObserver<NodeA>();
auto observerB = OutputtingObserver<NodeB>();
auto parser = Parser();
parser.Attach(observerA);
parser.Attach(observerB);
parser.Attach(observerA);
parser.Parse();
return 1;
}
您需要在此处使用组合并为每种类型的节点设置一个主题。但是,这是编译时验证的,所以我更喜欢这个而不是第二个版本。
解决方案 #2:动态且更接近您想要的
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
class ObserverBase
{
public:
virtual ~ObserverBase() = default;
};
template<typename TAttribute>
class Observer : public ObserverBase
{
public:
virtual void Observe(TAttribute& attribute) = 0;
};
template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
void Observe(TAttribute& attribute)
{
std::cout << attribute << std::endl;
}
~OutputtingObserver() = default;
};
template<typename TKey>
class Subject
{
private:
using ObserverList = std::vector<ObserverBase*>;
using ObserverMap = std::unordered_map<TKey, ObserverList>;
ObserverMap mutable m_observers;
public:
void Attach(TKey key, ObserverBase& observer) const
{
auto itr = m_observers.find(key);
if (itr == m_observers.end())
{
m_observers.emplace(std::make_pair(key, ObserverList { &observer }));
return;
}
itr->second.push_back(&observer);
}
void Detach(ObserverBase& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
template<TKey key, typename TAttribute>
void Notify(TAttribute& attribute)
{
auto itr = m_observers.find(key);
if (itr == m_observers.end())
return;
for (auto observer : itr->second)
dynamic_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
}
};
enum class NodeType
{
TypeA,
TypeB
};
class NodeA
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
{
return o << "a";
}
};
class NodeB
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
{
return o << "b";
}
};
class Parser
{
private:
Subject<NodeType> m_subject;
public:
void Parse()
{
auto a = NodeA();
auto b = NodeB();
m_subject.Notify<NodeType::TypeA, NodeA>(a);
m_subject.Notify<NodeType::TypeB, NodeB>(b);
}
void Attach(Observer<NodeA>& observer)
{
m_subject.Attach(NodeType::TypeA, observer);
}
void Attach(Observer<NodeB>& observer)
{
m_subject.Attach(NodeType::TypeB, observer);
}
};
int main()
{
auto observerA = OutputtingObserver<NodeA>();
auto observerB = OutputtingObserver<NodeB>();
auto parser = Parser();
parser.Attach(observerA);
parser.Attach(observerB);
parser.Attach(observerA);
parser.Parse();
return 1;
}
这更接近您的版本。非常不安全,速度较慢,但打字次数略少。
总结
两个输出 a\na\nb
和两者都是缝合在一起的东西,只是作为一个最小的概念证明,而不是你应该遵循的东西(尤其是使用 unordered_map 感觉很讨厌)。
这不是你想要的,但我想你可以从那里拿走它...
我强烈认为有更好的解决方案,所以请随意尝试。
编辑:
解决方案 #3:完全动态
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <typeindex>
template<typename TAttribute>
class Observer
{
public:
virtual void Observe(TAttribute& attribute) = 0;
virtual ~Observer() = default;
};
class Subject
{
private:
using ObserverList = std::vector<void*>;
using ObserverMap = std::unordered_map<std::type_index, ObserverList>;
ObserverMap mutable m_observers;
public:
template<typename TAttribute>
void Attach(Observer<TAttribute>& observer) const
{
auto index = std::type_index(typeid(Observer<TAttribute>));
auto itr = m_observers.find(index);
if (itr == m_observers.end())
{
m_observers.emplace(std::make_pair(index, ObserverList { &observer }));
return;
}
itr->second.push_back(&observer);
}
template<typename TAttribute>
void Detach(Observer<TAttribute>& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
template<typename TAttribute>
void Notify(TAttribute& attribute)
{
auto itr = m_observers.find(std::type_index(typeid(Observer<TAttribute>)));
if (itr == m_observers.end())
return;
for (auto observer : itr->second)
static_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
}
};
这基本上是 Dictionary<Type, Object>
的 C# 移植版本,它使用 rtti,所以你可能会被 C++ 强硬派吐槽...
我正在开发一个库来解析某个 XML 文件(使用 RapidXML)和 returns 我的一个对象,其中包含该文件数据。 XML 文件是由其他人的应用程序创建的。我需要使用观察者模式,因为速度非常重要,例如:
假设一个文件有 10.000 个标签及其子节点。在一个简单的解析器中,它们将按照它们被发现的顺序添加到 std::vector 中。然后,在文件被解析后,我们需要再次迭代这 10.000 个值,并用它们做任何我们想做的事。
通过使用观察者模式,我允许外部观察者(无论哪个 class 想要观察和通知每个获取的节点都必须继承我的库附带的 AbstractObserver,并实现他的功能)到成为解析过程的一部分,而不需要在解析的节点上再次迭代 X 次。 HOWEVER... 有多种节点,例如:tileset, layer, imagelayer 等等(根据Observer/Subject模式,其对应节点需要多个onObserved和notify函数,可能有很多'duplicated' 代码 - 注意:不使用继承。请参阅下面的 'bad' 示例)。我可以简单地让节点继承自某种 BaseNode class,但我不想在这里使用继承,因为我不想处理指针。相反,我使用枚举来键入节点,这就是我的问题所在。
/* ################## ABSTRACT OBSERVER #################### */
// Implements the observer pattern, using the CRTP pattern
template<class ConcreteObserver>
class AbstractObserver
{
public:
virtual ~AbstractObserver() { }
template<class Attribute>
inline void onObserved(Attribute attribute) {
// This requires ConcreteObserver to have a method onObservedImpl<Attribute>
static_cast<const ConcreteObserver*>(this)->onObservedImpl(attribute);
}
};
/* ################## ABSTRACT SUBJECT #################### */
class AbstractSubject
{
public:
virtual ~AbstractSubject() { }
// ???????
inline void attach(AbstractObserver<??????>* observer) {
m_observers.push_back(observer);
}
// ???????
inline void detatch(AbstractObserver<??????>* observer) {
auto& it = std::find(m_observers.begin(), m_observers.end(), observer);
// Remove the observer from the list, if it was observing
if (it != m_observers.end())
m_observers.erase(it);
}
protected:
template<typename Attribute>
void notify(Attribute& attribute) {
for (auto* observer : m_observers)
observer->onObserved(attribute)
}
private:
// ???????
std::vector<AbstractObserver<??????>*> m_observers;
};
/* ################## CONCRETE OBSERVER #################### */
class SomeConcreteObserver : public AbstractObserver<SomeConcreteObserver>
{
public:
// The CRTP 'virtual' function implementation
template<class Attribute>
void onObservedImpl(Attribute attribute)
{
// Filter the attribute and use it accordingly
switch (attribute.type)
{
// ....
}
}
};
/* ################## CONCRETE SUBJECT #################### */
class Parser : public AbstractSubject
{
public:
void parse(/* params */)
{
Foo f;
notify<Foo>(f);
// Later on....
Bar b;
notify<Bar>(b);
}
};
如我们所见,我也在使用 CRTP,因为我需要 'templated virtual functions',否则无法实现。由于 AbstractObserver 需要一个类型(因为 CRTP),我无法在 AbstractSubject class(见上文)中正确使用它们。甚至可以使用像 Java 或类似的匿名模板吗?我相信这会完成工作。
这是我想到的 'bad' 示例的实现,但这是我能针对这种情况想出的最佳示例:
// Remove the CRTP
class AbstractObserver
{
public:
virtual ~AbstractObserver() { }
virtual void onNodeA(NodeA n) = 0;
virtual void onNodeB(NodeB n) = 0;
virtual void onNodeC(NodeC n) = 0;
virtual void onNodeD(NodeD n) = 0;
virtual void onNodeE(NodeE n) = 0;
// .....
};
class AbstractSubject
{
public:
// ....
protected:
void notifyNodeA(NodeA n) {
for (auto* observer : m_observers)
observer->onNodeA(n);
}
void notifyNodeB(NodeB n) {
for (auto* observer : m_observers)
observer->NodeB(n);
}
void notifyNodeC(NodeC n) { }
void notifyNodeD(NodeD n) { }
void notifyNodeE(NodeE n) { }
// ....
private:
std::vector<Observer*> m_observers;
};
解决方案必须使用 C++11 或 bellow 并且没有提升。
解决方案 #1:非常潮湿但简单
#include <vector>
#include <algorithm>
#include <iostream>
template<typename TAttribute>
class Observer
{
public:
virtual void Observe(TAttribute& attribute) = 0;
virtual ~Observer() = default;
};
template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
void Observe(TAttribute& attribute)
{
std::cout << attribute << std::endl;
}
~OutputtingObserver() = default;
};
template<typename TAttribute>
class Subject
{
private:
std::vector<Observer<TAttribute>*> mutable m_observers;
public:
void Attach(Observer<TAttribute>& observer) const
{
m_observers.push_back(&observer);
}
void Detach(Observer<TAttribute>& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
void Notify(TAttribute& attribute)
{
for (auto observer : m_observers)
observer->Observe(attribute);
}
};
class NodeA
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
{
return o << "a";
}
};
class NodeB
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
{
return o << "b";
}
};
class Parser
{
private:
Subject<NodeA> m_subjectA;
Subject<NodeB> m_subjectB;
public:
void Parse()
{
auto a = NodeA();
auto b = NodeB();
m_subjectA.Notify(a);
m_subjectB.Notify(b);
}
void Attach(Observer<NodeA>& observer)
{
m_subjectA.Attach(observer);
}
void Attach(Observer<NodeB>& observer)
{
m_subjectB.Attach(observer);
}
};
int main()
{
auto observerA = OutputtingObserver<NodeA>();
auto observerB = OutputtingObserver<NodeB>();
auto parser = Parser();
parser.Attach(observerA);
parser.Attach(observerB);
parser.Attach(observerA);
parser.Parse();
return 1;
}
您需要在此处使用组合并为每种类型的节点设置一个主题。但是,这是编译时验证的,所以我更喜欢这个而不是第二个版本。
解决方案 #2:动态且更接近您想要的
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
class ObserverBase
{
public:
virtual ~ObserverBase() = default;
};
template<typename TAttribute>
class Observer : public ObserverBase
{
public:
virtual void Observe(TAttribute& attribute) = 0;
};
template<typename TAttribute>
class OutputtingObserver : public Observer<TAttribute>
{
public:
void Observe(TAttribute& attribute)
{
std::cout << attribute << std::endl;
}
~OutputtingObserver() = default;
};
template<typename TKey>
class Subject
{
private:
using ObserverList = std::vector<ObserverBase*>;
using ObserverMap = std::unordered_map<TKey, ObserverList>;
ObserverMap mutable m_observers;
public:
void Attach(TKey key, ObserverBase& observer) const
{
auto itr = m_observers.find(key);
if (itr == m_observers.end())
{
m_observers.emplace(std::make_pair(key, ObserverList { &observer }));
return;
}
itr->second.push_back(&observer);
}
void Detach(ObserverBase& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
template<TKey key, typename TAttribute>
void Notify(TAttribute& attribute)
{
auto itr = m_observers.find(key);
if (itr == m_observers.end())
return;
for (auto observer : itr->second)
dynamic_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
}
};
enum class NodeType
{
TypeA,
TypeB
};
class NodeA
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeA& node)
{
return o << "a";
}
};
class NodeB
{
public:
friend std::ostream& operator<<(std::ostream& o, const NodeB& node)
{
return o << "b";
}
};
class Parser
{
private:
Subject<NodeType> m_subject;
public:
void Parse()
{
auto a = NodeA();
auto b = NodeB();
m_subject.Notify<NodeType::TypeA, NodeA>(a);
m_subject.Notify<NodeType::TypeB, NodeB>(b);
}
void Attach(Observer<NodeA>& observer)
{
m_subject.Attach(NodeType::TypeA, observer);
}
void Attach(Observer<NodeB>& observer)
{
m_subject.Attach(NodeType::TypeB, observer);
}
};
int main()
{
auto observerA = OutputtingObserver<NodeA>();
auto observerB = OutputtingObserver<NodeB>();
auto parser = Parser();
parser.Attach(observerA);
parser.Attach(observerB);
parser.Attach(observerA);
parser.Parse();
return 1;
}
这更接近您的版本。非常不安全,速度较慢,但打字次数略少。
总结
两个输出 a\na\nb
和两者都是缝合在一起的东西,只是作为一个最小的概念证明,而不是你应该遵循的东西(尤其是使用 unordered_map 感觉很讨厌)。
这不是你想要的,但我想你可以从那里拿走它...
我强烈认为有更好的解决方案,所以请随意尝试。
编辑:
解决方案 #3:完全动态
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <typeindex>
template<typename TAttribute>
class Observer
{
public:
virtual void Observe(TAttribute& attribute) = 0;
virtual ~Observer() = default;
};
class Subject
{
private:
using ObserverList = std::vector<void*>;
using ObserverMap = std::unordered_map<std::type_index, ObserverList>;
ObserverMap mutable m_observers;
public:
template<typename TAttribute>
void Attach(Observer<TAttribute>& observer) const
{
auto index = std::type_index(typeid(Observer<TAttribute>));
auto itr = m_observers.find(index);
if (itr == m_observers.end())
{
m_observers.emplace(std::make_pair(index, ObserverList { &observer }));
return;
}
itr->second.push_back(&observer);
}
template<typename TAttribute>
void Detach(Observer<TAttribute>& observer) const
{
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
}
template<typename TAttribute>
void Notify(TAttribute& attribute)
{
auto itr = m_observers.find(std::type_index(typeid(Observer<TAttribute>)));
if (itr == m_observers.end())
return;
for (auto observer : itr->second)
static_cast<Observer<TAttribute>*>(observer)->Observe(attribute);
}
};
这基本上是 Dictionary<Type, Object>
的 C# 移植版本,它使用 rtti,所以你可能会被 C++ 强硬派吐槽...