2013-07-29 31 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