我正在使用“观察者”设计模式来管理当前正在开发的游戏中的事件。

我以此处演示的实现为基础,但对其进行了改进以便于使用。 />
class Observer
{
public:
    virtual ~Observer() {}

    virtual void onNotify(Subject * entity, Event event) = 0;

private:
};

class Subject
{
public:
    Subject() {}

    virtual ~Subject() {}

    void addObserver(Observer* observer)
    {
        if (std::find(_observers.begin(), _observers.end(), observer) == _observers.end())
        {
            _observers.push_back(observer);
        }
    }

    void removeObserver(Observer* observer)
    {
        std::list<Observer*>::iterator it = std::find(_observers.begin(), _observers.end(), observer);
        if (it != _observers.end())
        {
            *it = NULL;
            _eraseQueue.push(it);
        }
    }

protected:
    // I'm passing the Subject, because it allows me to trigger an event
    // on a newly created object, from another class :
    // this->notify(new MyClass(...), MYCLASS_CREATED);
    // It allows me to generate new entities easily
    void notify(Subject * entity, Event event)
    {
        for (std::list<Observer*>::iterator it = _observers.begin(); it != _observers.end(); ++it)
        {
            if (*it != NULL)
                (*it)->onNotify(entity, event);
        }

        while (!_eraseQueue.empty())
        {
            _observers.erase(_eraseQueue.front());
            _eraseQueue.pop();
        }
    }

    void notify(Subject * entity, Event event, Observer* observer)
    {
        if (observer != NULL)
            observer->onNotify(entity, event);
    }

private:
    std::list<Observer*>                        _observers;
    std::queue<std::list<Observer*>::iterator>  _eraseQueue;
};

template <typename T>
class EventHandler : public Observer
{
public:
    virtual ~EventHandler() {}

    virtual void onNotify(Subject * entity, Event event)
    {
        if (dynamic_cast<T*>(this))
        {
            auto it = _actions.find(event);

            if (it != _actions.end())
            {
                (dynamic_cast<T*>(this)->*(it->second))(entity);
            }
        }
    }

protected:
    template <typename U>
    U safe_cast(Subject* entity)
    {
        if (dynamic_cast<U>(entity))
            return (dynamic_cast<U>(entity));
        else
            throw std::exception("Event thrown on not-matching entity");
    }

protected:
    std::map<const Event, void (T::*)(Subject *)>   _actions;
};


以下是一些使它与众不同的解释:


Subject的观察者列表中删除元素时,我将观察者存储到要删除的擦除队列中,因此即使在notify调用中移除观察者时,notify方法中的迭代器仍然可以使用。

“观察者”不继承自Observer直接在内部,但是它们从EventHandler继承,Observer是一个模板化类,允许我在接收事件时自动调用相应的指针以进行操作,而不必在每个EventHandler的daugther类中进行管理。
safe_cast类实现一种Subject方法,这样我就可以转换从onNotify收到的EventHandler了因此,也许您想知道为什么我使用的是Observer类而不是直接从EventHandler类继承,所以这可能是一个奇怪的问题。一个简单的示例,向您展示为什么它有用,并使代码更易于阅读:

MyClass::MyClass()
{
    _actions[GAME_STARTED] = &MyClass::gameStarted;
    _actions[CHARACTER_MOVED] = &MyClass::characterMoved;
}

void MyClass::gameStarted(Subject * entity)
{
    Game* game = safe_cast<Game*>(entity);

    // Do actions on the Game instance
}

void MyClass::characterMoved(Subject * entity)
{
    Character* character = safe_cast<Character*>(entity);

    // Do actions on the Character instance
}


就这样。我的事件处理功能对最终用户完全是隐藏的,他只需要将自己想听的事件存储在班级中,它将由Subject类自动处理。

我已经知道改善我的Observer模式实现的几种方法:


使用智能指针,
实际上,使用引用代替指针,
处理notify实例的情况在onNotify方法调用期间被删除。

为了阐明EventHandler类的作用,这是我从中继承的方法:

class MyClass : public EventHandler<MyClass>


然后,对safe_cast方法进行一些解释(onNotify方法实际上并不是此类的主要目的,并且似乎很清楚):

virtual void onNotify(Subject * entity, Event event)
{
    // Verify that the 'this' pointer is of type T
    if (dynamic_cast<T*>(this))
    {
        // Find the pointer to function corresponding to the 'event' Event if it has been registered
        auto it = _actions.find(event);

        // Check if the pointer to function has been found
        if (it != _actions.end())
        {
            // Calls the method on the dynamically casted 'this' pointer, so that it can
            // generically call private methods on any class inheriting from EventHandler
            (dynamic_cast<T*>(this)->*(it->second))(entity);
        }
    }
}


我的使用方式:

// MyClass is inheriting from a 'self-templated' EventHandler
class MyClass : public EventHandler<MyClass>
{
public:
    MyClass() {
        // I'm registering in the EventHandler _actions tab the
        // events that this particular class is listening to
        _actions[CHARACTER_MOVED] = &MyClass::characterMoved;
        _actions[CHARACTER_DIED] = &MyClass::characterDied;

        // Some other initialization
    };
    virtual ~MyClass(){}

private:
    void characterMoved(Subject* entity) {
        // I'm converting the generic Subject object to a Character object
        Character* character = safe_cast<Character*>(entity);

        // I'm doing whatever I want with the character that triggered the event
        std::cout << character->getName() << " moved !" << std::endl;
    }
    void characterDied(Subject* entity) {
        // I'm converting the generic Subject object to a Character object
        Character* character = safe_cast<Character*>(entity);

        // I'm doing whatever I want with the character that triggered the event
        std::cout << "Oh no ! " << character->getName() << " died ! RIP" << std::endl;
        delete character;
    }
}


这使我免于在继承自Observer的每个类中实现q4312079q方法,并且使事件的处理完全隐藏了从最终用户那里,只需要在其构造函数中注册其类正在侦听的事件即可。

但是我想请您对我可以改进的其他方面以及您的总体看法对此实现感觉。

#1 楼

最好不要使用指针。

指针不代表所有权。因此用户不知道是否应该传递真实的指针(指向动态分配的对象),或者是否可以提供对象的地址。因此,您必须查看代码(或文档)以了解如何调用该代码。

// Subject is a reference
virtual void onNotify(Subject& entity, Event event) = 0;

// Since you registered on a subject the subject 
// will never been NULL so you can safely use a reference.


注册观察者时,这同样适用于Subject

// The observer can never be NULL  so you can safely pass by reference.
void addObserver(Observer& observer)

// Note:
// You still store the observer as a pointer in your container.
std::list<Observer*>                        _observers;

// So just get the address from the reference.


删除观察者问题

移除观察者时,为什么将迭代存储在擦除队列中?

我不知道认为您不需要这样做。

    {
        *it = NULL;
        _eraseQueue.push(it);
    }


只需阅读您的解释即可。这还算公平。我可能做的不一样。获取onNotify()方法以返回一个特殊值,该值意味着从观察者队列中删除。

通知问题

为什么要将subject传递给notify方法? br />
void notify(Subject * entity, Event event)


这不是当前主题的通知。因此,界面将是:

void notify(Event event)


然后调用所有观察者的onNotify()并将*this作为主题成员传递给所有人。

事件处理程序

我不太清楚事件处理程序中发生了什么。我相信它很好,但是我认为在发布库时,需要一些注释和示例(即使是您自己也可以,因为在6个月内,很难记住编写时的想法)。 br />安全强制转换。

不确定是否需要这样做。如果a不是X类型或不是从X(指针)派生的,则抛出,但返回dynamic_cast<X>(a)

因此,您可以使用引用而不是指针来实现相同的效果。 :: function

您正在存储指向成员函数的指针。可以,但是有点旧风格。使用dynamic_cast<X*>(a)并存储lambda是“正常的”现象。

std::map<const Event, void (T::*)(Subject *)>   _actions;

// I would use:
std::map<const Event, std::function<void(Subject&)>>   _actions;


下划线

我个人不喜欢在标识符前加上'_'。关于将其用作前缀的规则很重要。大多数人不了解这些规则,因此即使您确实知道其他人也必须考虑(或查找它们)这些规则以确保您没有弄错。使用'_'作为标识符的前缀。

在C ++标识符中使用下划线的规则是什么?

使用std :: function
// In EventHandler:
std::map<const Event, std::function<void(Subject&)>>   _actions;

// Register Handler (in the `Character class`)
_actions[TAKE_DAMAGE] = std::bind(&Character::takeDamage, this);

// So using the `std::function<>` technique you can use methods and
// lambdas. Because the code for calling the code is encapsulated in
// the standard function you don't need to handle the dangerous code
// involved with casting (this is done for you).


立即调用代码。

auto find = _actions.find(event);
if (find != _actions.end()) {
    find->second(subject, event);
}


示例:

下面的通知:


用于注册方法的事件处理程序的调用。
用于注册lambda的调用。
不需要dynamic_cast(或执行同样的事情)。
没有“好奇地重复的模板模式”
没有运行时类型检查(在事件处理程序上)

示例: />

评论


\ $ \ begingroup \ $
@Galithiel:您仍然可以使用std :: function <>并在派生类型上注册方法(以一种非常安全的方式)。往上看
\ $ \ endgroup \ $
–马丁·约克
2015年5月30日14:22

#2 楼

Loki提出了很好的观点,我认为我们可以进一步扩展。

在查看函数对象时,有几件事情要考虑,c ++ 11中的一件整洁的事情等等。 lambdas,在我的谦卑尝试中最终支持了以下代码:将unsubscribe方法插入函数对象后,该方法将无法正常工作。就我个人而言,我正在“实验”一个返回“订阅ID”的版本,该版本将用作取消订阅的参数。

要考虑的另一件事是可观察模式的“模板化”版本,其中大多数一旦开始将其用于不同的事件类型,就可以避免某些开销并减少大量代码。

您可以在另一个审阅请求中找到我对“模板化”版本的谦虚尝试:现代C ++中的可观察模板