【发布时间】:2015-10-24 17:11:26
【问题描述】:
我正在开发一个解析某个 XML 文件(使用 RapidXML)并返回包含该文件数据的我的对象的库。该 XML 文件是由其他人的应用程序创建的。我需要使用观察者模式,因为速度至关重要,例如:
假设一个文件有 10.000 个标签及其子节点。在一个简单的解析器中,它们会按照它们被发现的顺序被添加到 std::vector 中。然后,在文件被解析后,我们需要再次遍历这 10.000 个值,然后对它们做任何我们想做的事情。
通过使用观察者模式,我允许外部观察者(任何想要观察并被通知每个获取的节点的类都必须从我的库附带的 AbstractObserver 继承,并实现他的功能)成为解析的一部分无需在解析的节点上再次迭代 X 次。但是......有多种节点,例如:tileset,layer,imagelayer等(根据Observer/Subject模式,其对应节点需要多个onObserved和notify函数,可能有很多'重复' 代码 - 注意:不使用继承。请参阅下面的“坏”示例)。我可以简单地让节点从某种 BaseNode 类继承,但我不想在这里使用继承,因为我不想处理指针。相反,我使用枚举来键入节点,这就是我的问题所在。
/* ################## 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,因为我需要“模板化虚函数”,否则无法实现。由于 AbstractObserver 需要一个类型(因为 CRTP),我不能在 AbstractSubject 类中正确使用它们(见上文)。甚至可以使用像 Java 或类似的匿名模板吗?我相信这会完成这项工作。
这是我想到的一个“坏”示例的实现,但这是针对这种情况我能想到的最好的例子:
// 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 或以下版本,并且没有任何提升。
【问题讨论】:
-
请问您为什么反对“指针”?
-
恐怕这不是泛型的正确用法——它在 java 中工作只是因为 java 将它全部向上转换为对象然后进行类型检查。如果您坚持的话,您也可以在 C++ 中执行此操作(void* + dynamic_cast)...
-
@user3159253 你好。很复杂,我试着解释一下。有这个xml文件。我的文件类基本上是一堆不同类型的向量(内容是静态的)。会有一些我不知道的外部类会消耗我的文件对象。文件类可以有指针向量,是的,然后在 dtor 上“删除”它们。如果文件有指针并且它们都将在 dtor 上被销毁,那么消费者类也会使指针失效,除非他们在他们的类中保留指向文件的引用/指针,我不“推荐”(这就是为什么文件中没有指针)
-
@Alegnem 我知道。这只是一个简单的例子,说明它是如何工作的。我为此找到了 2 个解决方案,即从基类继承节点并将指针存储在文件类中,或者为每个 xml 节点使用多个 onNotified 和 notifyX 函数(这听起来很错误),尽管我在问是否有这样做的好方法
标签: c++ templates c++11 observer-pattern crtp