9

我有一个动画课。我需要在动画中有一些观察者和Play事件。我为这个问题找到了 2 个解决方案,但我不知道该选择什么。PauseStop

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

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

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

优点 1。

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

1的缺点。

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

2的优势。

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

2的缺点。

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

你能告诉我用什么吗?根据您的经验,对于这个问题有什么更好的方法 - 第一种方法的自由或第二种方法的清晰易懂的代码?您能否给我两种方法或其他解决方案的其他优点/缺点?

4

3 回答 3

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();
}

你可以在这里试试。特别是,此示例使用策略类(因此没有“正式”定义接口,您可以“丰富”接口,就像 setPlayHandle 所做的那样)。但是,您也可以对运行时绑定执行类似的操作。

于 2013-07-29T12:54:13.933 回答
1

对于除了最简单的玩具示例之外的所有示例,我认为Boost.Signals2将是更好的解决方案。它是经过精心设计、经过良好测试和有据可查的。重新发明轮子非常适合家庭作业类型的练习,但不适用于生产代码。例如,让您自己的观察者线程安全对于获得正确和高效来说并非易事。

特别讨论您列出的缺点

  • 您可以编写 C++11 lambdas 而不是命名函数对象或使用boost::bind语法(无论如何,这对于大多数用法来说并不复杂)
  • 我不太明白你关于未使用事件的观点。您可以进行非常高级的连接管理来查询和断开来自插槽的信号。

TL;DR:熟悉 Boost.Signals2

于 2013-07-29T14:29:17.293 回答
0

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

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

于 2013-07-29T12:59:36.833 回答