2013-07-29 41 views
9

Tôi có một lớp Hoạt ảnh. Tôi cần có một số nhà quan sát cho các sự kiện Play, PauseStop trong Hoạt ảnh. Tôi đã tìm thấy 2 giải pháp cho vấn đề này, nhưng tôi không biết phải chọn cái gì.Cách triển khai mẫu quan sát trong C++

  1. Sử dụng boost :: tín hiệu hoặc một cái gì đó tương tự và đăng ký callbacks cho mỗi sự kiện

  2. Thực hiện một giao diện đơn giản với 3 chức năng thuần túy ảo (OnPlay(), OnPause(), OnStop()) và chuyển cho các đối tượng lớp Animation rằng triển khai giao diện này.

Có những ưu điểm và nhược điểm cho mọi phương pháp. Tôi sẽ cố gắng liệt kê những cái mà tôi tìm thấy cho đến nay:

Ưu điểm cho 1.

  • tôi có thể sử dụng bất kỳ chức năng thành viên/chức năng miễn phí như một callback
  • tôi không có để thực hiện tất cả 3 chức năng nếu tôi không quan tâm đến tất cả chúng
  • Cùng đối tượng có thể được sử dụng như quan sát viên cho nhiều hình ảnh động mà không đi tham số bổ sung từ lớp Animation

Nhược điểm cho 1.

  • tôi phải tạo ra một đối tượng có thể được gọi cho mỗi callback
  • Nếu tôi muốn bổ sung thêm sau một sự kiện mới nó sẽ khó có thể tìm ra nơi mà nó được sử dụng (trình biên dịch không thể thực thi tôi để thực hiện hoặc bỏ qua sự kiện mới).
  • Cú pháp lạ nào đó (tôi phải sử dụng lệnh std :: bind/boost :: bind ở mọi nơi).

Ưu điểm cho 2.

  • Dễ hiểu xây dựng
  • Nếu tôi sẽ thêm một sự kiện mới trong lớp giao diện Animation/Observer trình biên dịch sẽ thực thi tôi để thực hiện (trống có thể) chức năng mới.

Nhược điểm cho 2.

  • tôi phải thực hiện (trống có thể) 3 chức năng ngay cả khi tôi sẽ chỉ sử dụng một
  • Cùng đối tượng không thể được sử dụng như quan sát viên cho nhau hoạt hình mà không gửi một số tham số bổ sung từ hoạt ảnh (ID hoặc thứ gì đó).
  • Không thể sử dụng các chức năng miễn phí.

Bạn có thể vui lòng cho tôi biết nên sử dụng gì không? Từ kinh nghiệm của bạn những gì là tốt hơn cho vấn đề này - sự tự do từ aproach đầu tiên hoặc rõ ràng và dễ hiểu mã từ thứ hai?Bạn có thể vui lòng cho tôi những ưu điểm/nhược điểm khác cho cả hai phương pháp hoặc giải pháp khác không?

+6

Trong C++ 11 (mà tôi giả sử bạn có thể sử dụng, kể từ khi bạn gắn thẻ câu hỏi với nó) lambdas loại bỏ hầu hết các "nhược điểm cho 1 ". –

+0

Nếu sử dụng 'std :: bind' là cú pháp lạ đối với bạn (đặc biệt là so với giao diện với các hàm ảo 'OnWhatever'), thì bạn nên xem xét lại ngôn ngữ bạn chọn. –

+0

@ChristianRau Không phải cho tôi, nhưng tôi không phải là người duy nhất làm việc trên cơ sở mã. – Felics

Trả lời

3

Trước hết, sẽ hữu ích nếu biết "ràng buộc" được biết tại thời gian biên dịch hay không. Nếu vậy, tôi sẽ đề nghị bạn xem xét các lớp chính sách. Ngoài ra, tôi sẽ đi cho một kết hợp của hai giải pháp, tức là sử dụng cách tiếp cận giao diện và thực hiện một giao diện đóng vai trò như một trình phát lại cho tín hiệu/chức năng tự do. Bằng cách này bạn có thể có các hành vi mặc định, bạn có thể thêm các đối tượng tùy chỉnh thực hiện toàn bộ giao diện và về cơ bản, các ưu điểm của hai phương pháp cũng như tính linh hoạt nhiều.

Dưới đây là một ví dụ cơ bản về cách tiếp cận được đề xuất, tôi hy vọng nó là trợ giúp.

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

bạn có thể thử here. Ví dụ này, đặc biệt, sử dụng một lớp chính sách (do đó không có giao diện nào được "chính thức" định nghĩa, và bạn có thể "làm giàu" giao diện, như được thực hiện với setPlayHandle). Tuy nhiên, bạn có thể làm một cái gì đó tương tự với ràng buộc thời gian chạy quá.

0

Tôi nghĩ, Bạn có thể sử dụng cả hai :) nhưng tùy thuộc vào nhu cầu. Tôi đã có một số mã mà tôi sử dụng cả hai mẫu này. Có rất nhiều hàm được gọi là onSomething() (onMouseButton(), onKey(), onDragStart() v.v.), nhưng cũng có các callback. Khi tôi cần thực hiện một số hành vi, nhưng đối với toàn bộ lớp đối tượng, tôi sử dụng phương thức onSomething(). Nhưng nếu tôi có một loạt các đối tượng của cùng một lớp nhưng chỉ một phần của chúng cần chức năng mở rộng - gọi lại là một cách hoàn hảo để đi.

Khi triển khai, thực hiện như sau: có một số mã gửi cố gắng sử dụng phương thức onSomething() (trả về bool), nếu kết quả là false - thì kiểm tra xem có gọi lại không, nếu có, nó được thực hiện.

1

Đối với tất cả trừ những ví dụ đồ chơi đơn giản nhất, Boost.Signals2 sẽ là giải pháp vượt trội theo ý kiến ​​của tôi. Nó được thiết kế tốt, được kiểm tra kỹ lưỡng và có tài liệu tốt. Tái phát minh bánh xe là tốt cho loại bài tập về nhà, nhưng không phải cho mã sản xuất. Ví dụ. làm cho máy chủ của riêng bạn an toàn là không tầm thường để có được quyền và hiệu quả.

Cụ thể thảo luận bất lợi liệt kê của bạn

  • bạn có thể viết C++ 11 lambdas thay vì đối tượng hàm có tên hoặc sử dụng boost::bind cú pháp (mà là không thực sự phức tạp đối với hầu hết sử dụng anyway)
  • tôi không khá hiểu điểm của bạn về các sự kiện không được sử dụng. Bạn có thể thực hiện khá tiên tiến connnection management để truy vấn và ngắt kết nối tín hiệu khỏi các vị trí.

TL; DR: làm quen với Boost.Signals2

+0

Tôi sẽ ngần ngại nói rằng Boost.Signals hoặc Boost.Signals2 là "hiệu quả" từ khía cạnh hiệu suất. Tuy nhiên, tôi đồng ý rằng chúng được ghi chép và bảo trì kỹ lưỡng. @ OP: Bạn thực sự nên thử đi cả hai cách với điều này như bạn sẽ tìm hiểu xem ưu và khuyết điểm ban đầu của bạn của mỗi thực sự nắm giữ lên đến _your_ mong đợi.Đối với một thư viện tín hiệu, hãy chọn một thư viện được duy trì thường xuyên, có tài liệu tốt và các yêu cầu an toàn chủ đề có thể có, một thư viện là chủ đề an toàn. – ApEk

+0

@ApEk Tôi sẽ nói rằng (gần như) tất cả các thư viện Boost đều hiệu quả * được cung cấp * và lý do duy nhất không sử dụng chúng là đôi khi chúng có thể cung cấp nhiều hơn mức bạn cần và sẽ không sử dụng. – TemplateRex

Các vấn đề liên quan