2013-07-29 4 views
9

У меня есть один класс анимации. Мне нужно иметь некоторых наблюдателей за Play, Pause и Stop событий в анимации. Я нашел 2 решения этой проблемы, но я не знаю, что выбрать.Как реализовать шаблон наблюдателя в C++

  1. Использование подталкивания :: сигналы или что-то подобное и зарегистрировать функции обратного вызова для каждого события

  2. Сделать простой интерфейс с 3-х чистыми виртуальными функциями (OnPlay(), OnPause(), OnStop()) и перейти к объектам анимации класса, которые реализовать этот интерфейс.

Есть преимущества и недостатки для каждого метода. Попробую перечислить те, которые я нашел до сих пор:

Преимущество для 1.

  • Я могу использовать любую функцию-члена/свободную функцию обратного вызова
  • у меня нет реализовать все 3 функции, если я не забочусь о них всех
  • же объект может быть использован в качестве наблюдателя для нескольких анимация без прохождения дополнительных параметров из класса Animation

Недостатков для 1.

  • Я должен создать вызываемый объект для каждого обратного вызова
  • Если я хочу, чтобы добавить позже новое событие будет трудно найти места, где он был использован (компилятор не может заставить меня реализовать или проигнорировать новое событие).
  • Как-то странный синтаксис (мне нужно использовать std :: bind/boost :: bind везде).

Преимущества 2.

  • Легко понять конструкцию
  • Если я добавить новое событие в классе интерфейса Анимация/Observer компилятор будет проводить в жизнь мне реализовать (пустой возможно) новая функция.

Недостатки для 2.

  • Я должен реализовать (пусто возможно) 3 функции, даже если я буду использовать только один
  • же объект не может быть использован в качестве наблюдателя для различных анимация без отправки некоторого дополнительного параметра из анимации (идентификатор или что-то еще).
  • Невозможно использовать свободные функции.

Не могли бы вы посоветовать мне, что использовать? Из вашего опыта, что лучше для этой проблемы - свобода от первого априорного или понятного и понятного кода от второго?Можете ли вы дать мне другие преимущества/недостатки для обоих методов или другого решения?

+6

В C++ 11 (который я предполагаю, что вы можете использовать, так как вы помечено вопрос с ним) лямбдах удалить большинство из «недостатков для 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() (который возвращает bool), если он является результатом, является ложным - тогда есть проверка, если обратный вызов определен, если да, он выполнен.

1

Для всех, кроме простейших примеров игрушек, Boost.Signals2 будет лучшим решением на мой взгляд. Он хорошо разработан, хорошо протестирован и хорошо документирован. Переосмыслить колесо приятно для домашнего задания, но не для производственного кода. Например. что делает ваш собственный поток потокобезопасных пользователей нецелесообразным, чтобы получить право и эффективность.

В частности обсуждать ваши перечисленные недостатки

  • вы можете написать C++ 11 лямбды вместо названных функциональных объектов или с помощью boost::bind синтаксиса (который на самом деле не сложно для большинства случаев использования в любом случае)
  • я не вполне понимаете свою точку зрения о неиспользуемых событиях. Вы можете сделать довольно продвинутый connnection management для запроса и отключения сигналов от слотов.

TL; DR: ознакомиться с Boost.Signals2

+0

Я бы с сомнением сказал, что либо Boost.Signals, либо Boost.Signals2 являются «эффективными» с точки зрения производительности. Однако я согласен с тем, что они тщательно документированы и поддерживаются. @OP: На самом деле вы должны попробовать в обоих направлениях, так как вы узнаете, действительно ли ваши оригинальные плюсы и минусы соответствуют вашим ожиданиям.Что касается библиотеки сигналов, выберите ту, которая регулярно поддерживается, имеет хорошую документацию и, для обеспечения требований безопасности потока, библиотеку, которая является потокобезопасной. – ApEk

+0

@ApEk Я бы сказал, что (почти) все библиотеки Boost эффективны * с учетом того, что они предлагают *, и единственная причина, по которой они не используются, - это то, что они могут иногда предлагать больше, чем вам нужно, и поэтому вы будете платить за то, что вы не будет использоваться. – TemplateRex

Смежные вопросы