如何在C ++中实现Observer模式

时间:2013-07-29 12:46:36

标签: c++ design-patterns c++11

我有一个动画类。我需要在动画中为PlayPauseStop事件设置一些观察者。我找到了2个解决这个问题的方法,但我不知道该选择什么。

  1. 使用boost :: signals或类似的东西并为每个事件注册回调

  2. 使用3个纯虚函数(OnPlay()OnPause()OnStop()创建一个简单的接口,并传递给实现此接口的Animation类对象。

  3. 每种方法都有优点和缺点。我将尝试列举到目前为止我发现的那些:

    1的优势。

    • 我可以使用任何成员函数/免费函数作为回调
    • 如果我不关心所有这些功能,我不必实现所有3个功能
    • 同一个对象可以用作多个动画的观察者,而无需从Animation类
    • 传递额外的参数

    1的缺点

    • 我必须为每个回调创建一个可调用对象
    • 如果我想稍后添加一个新事件,很难找到使用它的地方(编译器无法强制我实现或忽略新事件)。
    • 不知何故奇怪的语法(我必须在任何地方使用std :: bind / boost :: bind)。

    2的优点。

    • 易于理解的构造
    • 如果我在Animation / Observer接口类中添加一个新事件,编译器会强制我实现(可能是空的)新函数。

    2的缺点。

    • 即使我只使用一个
    • ,我也必须实现(空可能)3个功能
    • 如果不从动画中发送一些额外的参数(ID或其他内容),同一个对象不能用作不同动画的观察者。
    • 无法使用免费功能。

    你能告诉我使用什么吗?从您的经验来看,对于这个问题有什么好处 - 从第一个方面获得免费或者从第二个方面获得清晰易懂的代码?您能否告诉我这两种方法或其他解决方案的其他优点/缺点?

3 个答案:

答案 0 :(得分:3)

首先,知道在编译时是否知道“绑定”是有用的。如果是这样,我建议你研究政策类。

除此之外,我会考虑两种解决方案的混合,即使用接口方法并实现一个接口,充当信号/自由功能的中继器。通过这种方式,您可以拥有默认行为,您可以添加实现整个界面的自定义对象,并且基本上具有这两种方法的优点以及更大的灵活性。

以下是提议方法的基本示例,我希望它有所帮助。

#include <functional>

using namespace std;

template <class ObserverPolicy>
class Animation : public ObserverPolicy{

};

class MonolithicObserver{
    public:
    void play(){
        state = playing;
    }
    void pause(){
        if(playing == state)
            state = stopped;
    }
    void stop(){
        state = stopped;
    }
    private:
    enum {playing, paused, stopped} state;
};

struct doNothing{
    static void play(){}
    static void pause(){}
    static void stop(){}
};

struct throwException{
    class noPlay{};
    class noPause{};
    class noStop{};
    static void play(){
        throw noPlay();
    }
    static void pause(){
        throw noPause();
    }
    static void stop(){
        throw noStop();
    }
};

template <class DefaultPolicy = doNothing>
class FreeFunctionObserver{
    public:
    void play(){
        if(playHandle)
            playHandle();
        else
            DefaultPolicy::play();
    }
    void pause(){
        if(pauseHandle)
            pauseHandle();
        else
            DefaultPolicy::pause();
    }
    void stop(){
        if(stopHandle)
            stopHandle();
        else
            DefaultPolicy::stop();
    }
    void setPlayHandle(std::function<void(void)> p){
        playHandle = p;
    }
    void setPauseHandle(std::function<void(void)> p){
        pauseHandle = p;
    }
    void setStopHandle(std::function<void(void)> p){
        stopHandle = p;
    }
    private:
    std::function<void(void)> playHandle;
    std::function<void(void)> pauseHandle;
    std::function<void(void)> stopHandle;
};

void play(){}
void pause(){}
void stop(){}

int main(){
    Animation<FreeFunctionObserver<> > affo;
    affo.setPlayHandle(play);
    affo.setPauseHandle(pause);
    affo.setStopHandle(stop);
    affo.play();
    affo.pause();
    affo.stop();

    Animation<FreeFunctionObserver<throwException> > affot;
    try{
        affot.play();
    }
    catch(throwException::noPlay&){}

    Animation<MonolithicObserver> amo;
    amo.play();
    amo.pause();
    amo.stop();
}

您可以尝试here。特别是,这个例子使用了一个策略类(因此没有“正式”定义接口,你可以“丰富”接口,就像使用setPlayHandle一样)。但是,您也可以使用运行时绑定执行类似的操作。

答案 1 :(得分:1)

除了最简单的玩具示例之外,Boost.Signals2在我看来是最好的解决方案。它设计精良,经过充分测试并且记录完备。重新制作轮子对于家庭作业类型的练习很有用,但不适用于生产代码。例如。让你自己的观察者线程安全是非常重要的,以获得正确和有效。

具体讨论您列出的缺点

  • 你可以编写C ++ 11 lambdas而不是命名函数对象或者使用boost::bind语法(对于大多数用法来说这并不是很复杂)。
  • 我不太明白你对未使用事件的看法。您可以执行相当高级connnection management来查询和断开插槽中的信号。

TL; DR :熟悉Boost.Signals2

答案 2 :(得分:0)

我认为,您可以使用两者:)但这取决于需求。我有一些代码,我使用这两种模式。有许多函数叫做onSomething()(onMouseButton(),onKey(),onDragStart()等),但也有回调。当我需要实现某些行为时,但对于整个类的对象,我使用onSomething()方法。但是如果我有一堆相同类的对象,但只有部分对象需要扩展功能 - 回调是一个完美的方法。

在实施中,它是这样完成的: 有一些调度代码尝试使用onSomething()方法(返回bool),如果结果为false - 那么检查是否定义了回调,如果是,则执行。