2013-07-29 32 views
9

我有一个Animation类。我需要在动画中有一些观察者为Play,PauseStop事件。我发现了这个问题的两个解决方案,但我不知道该选什么。如何在C++中实现观察者模式

  1. 使用boost ::信号或类似的东西,并注册的回调,每一个事件

  2. 做一个简单的界面,与3个纯虚函数(OnPlay()OnPause()OnStop()),并传递给动画类对象实现这个接口。

每种方法都有其优点和缺点。我会尽力枚举我迄今发现的:

优势1.

  • 我可以使用任何成员函数/ free函数作为回调
  • 我没有实现所有3个功能,如果我不在乎所有的人
  • 同一个对象可以作为观察员多个动画,而不从动画类传递参数获取

缺点为1

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

优势2.

  • 易于理解建设
  • 如果我将在动画/ Observer接口类中添加一个新的事件,编译器将强制执行我实现(空也许)新的功能。

缺点为2

  • 我要实现(空可能),即使我会用3个功能只有一个
  • 同一个对象不能被用来作为观察员不同动画,而不需要从动画(ID或其他)发送一些额外的参数。
  • 不能使用自由功能。

您能否告诉我要使用什么?根据你的经验,对于这个问题有什么更好的解决方案 - 从第一个问题中解脱出来,还是从第二个问题中清楚和容易理解的代码?你能否给我两种方法或其他解决方案的其他优点/缺点?

+6

熟悉在C++ 11(我假设你可以使用,因为你用它标记的问题)lambda表达式除去大部分“弊端的1" 。 –

+0

如果使用'std :: bind'对你来说是一种奇怪的语法(特别是与具有虚拟'OnWhatever'功能的接口相比),那么你应该重新考虑你选择的语言。 –

+0

@ChristianRau不适合我,但我不是唯一一个在代码库上工作的人。 – Felics

回答

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来“丰富”接口)。但是,您也可以使用运行时绑定进行类似的操作。

0

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

在实施中,像这样做: 有哪些尝试使用onSomething()方法(它返回布尔)一些调度的代码,如果它的结果​​是假的 - 再有就是,检查是否回调定义如果是,它被执行。

1

除了最简单的玩具示例外,Boost.Signals2在我看来都是最好的解决方案。它设计精良,测试良好,文件齐全。重做轮子对于练习功课类型是很好的,但不适用于生产代码。例如。让自己的观察者线程安全并不重要,以确保正确和高效。

具体讨论你的缺点上市

  • 你可以写C++ 11 lambda表达式而不是命名函数对象或使用boost::bind语法(这是不是真的复杂,大多数使用反正)
  • 我不完全理解你对未使用事件的观点。您可以执行相当先进的connnection management来查询和断开插槽中的信号。

TL; DR:与Boost.Signals2

+0

我会毫不犹豫地说Boost.Signals或Boost.Signals2从性能方面来说是“高效”的。然而,我确实认为它们已被彻底记录和维护。 @OP:实际上你应该尝试两种方式,因为你会了解每个人的原始利弊是否符合你的期望。至于信号库,选择一个定期维护的,具有良好文档的,并且为了可能的线程安全要求,还需要一个线程安全的库。 – ApEk

+0

@ApEk我会说(几乎)所有的Boost库都是高效的*考虑到他们提供的*,而不使用它们的唯一原因是他们有时可以提供比你需要的更多的东西,所以你会支付你将不会使用。 – TemplateRex