使用 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++ 强硬派吐槽...