Шаблон Listener.
1. Зависимость классов в шаблоне Model-View-Controller.
1.1. Односторонняя зависимость на примере модели шахмат (из прошлой лекции по MVC).
В модели шахмат мы создали классы Model и View. В классе View (отображение фигур на экране) есть поле, указывающее на модель:
Model * model;
метод moveDone:
void moveDone() {
   model->move( x1, y1, x2, y2 );
   update();
}
и метод для отрисовки фигур – update.
В этом случае класс View зависит от класса Model, но не наоборот, то есть Model о классе View  ничего "не знает". А значит нет циклов в зависимостях классов друг от друга. При отсутствии таких циклов программа является более гибкой, и в ней легче заменять одни классы другими для изменения или улучшения программы.
1.2. Двусторонняя зависимость на примере модели часов.
Аналогично предыдущему примеру, создадим классы ClockModel и ClockView для модели и отображения часов соответственно.
class ClockModel {
public:
   int hour;
   int minute;
   int second;
   ClockView * view;
   void tick() {  //отсчет времени каждую секунду
      ...         //обновляем hour, minute, second
      view->update( this );
   }
   
};
class ClockView {  
public:  
   void update( ClockModel * m ) {  
       
       //отрисовка часов по полям hour, minute, second из model
   }
};
Видно что класс ClockView зависит от ClockModel, так при отрисовке необходимо обращаться к полям hour, minute и second, а класс ClockModel зависит от ClockView, так как вызывает у него метод update. Такая взаимная зависимость усложняет возможность замены классов и изменение программы, тем более другими программистами.
2. Шаблон Listener (слушатель).
2.1. Модель с одним слушателем.
Для разрешения циклических зависимостей классов существует шаблон Listener (он же Observer, он же Publish/Subscribe). С помощью этого шаблона можно реализовать классы так, что один класс может в некотором смысле "прослушивать" другой класс, то есть реагировать на происходящие в нем события.
Изменим наши классы ClockModel и ClockView таким образом, чтобы возможна была следующая запись:
ClockModel model;
ClockView view;
model.setListener( view );  //устанавливаем view слушателем model 
Теперь view является наблюдателем, а model – объектом наблюдения.
Если мы хотим устанавливать в качестве слушателей различные классы реализуем виртуальный класс Listener:
class Listener {
public:
   virtual void update( ClockModel * model ) = 0;
};
И наследуем от него классы отрисовки часов:
class AnalogClockView: public Listener {
public:
   void update( ClockModel * model ) {
      ...   //отрисовка стрелочных часов
   }
};
class DigitalClockView: public Listener {
public:
   void update( ClockModel * model ) {
      ...   //отрисовка цифровых часов
   }
};
А класс ClockModel будет реализован следующим образом:
class ClockModel {
public:
   int hour;
   int minute;
   int second;
   void tick() {  //отсчет времени каждую секунду
      ...         //обновляем hour, minute, second
      listener->update( this );
   }
   void setListener( Listener * l ) {
      listener = l;
   }
private:
   Listener * listener;
};
Теперь, наследуясь от класса Listener, и перегружая чисто виртуальный метод update, мы можем создавать новые классы для отображения часов.
2.2. Модель с несколькими слушателями.
Если нам одновременно хочется отображать на экране и аналоговые, и цифровые часы, имея при этом одну модель часов, добавим в ClockModel контейнер для слушателей, функции для их добавления, удаления и оповещения:
class ClockModel {
public:
   int hour;
   int minute;
   int second;
   void tick() {  //отсчет времени каждую секунду
      ...         //обновляем hour, minute, second
      
      notifyAll();  //сообщаем всем слушателям, о том что время изменилось
   }
   void addtListener( Listener * l ) {
      listeners.push_back( l );
   }
   void removeListener( Listener * l ) {
      listeners.erase( l );
   }
private:
   std::list < Listener * > listeners;  //контейнер для слушателей
   void notifyAll() {   //обновление всех слушателей
      for ( std::list < Listeners  * >::iterator it = listeners.begin(); it != listners.end(); it++ ) {
          it->update( this );
      }
   }
};
 Note: Если мы хотим, чтобы каждый слушатель был указан не больше одного раза, то лучше использовать контейнер std::set. 
С новым классом ClockModel возможен вывод двух часов одновременно:
ClockModel model;
AnalogClockView aView;
DigitalClockView dView;
model.addListener( aView );
model.addListener( dView ); 
В рассмотренных примерах зависимости между классами ClockView и ClockModel по прежнему остаются в обе стороны, но они неравнозначны. Класс ClockModel знает о классе отображения только то, что в нем есть метод update, то есть что он наследован от класса Listener.
 2.3. Изменение полей класса ClockModel внешними функциями.
 еализуем в классе AnalogClockView фунуцию для изменения времени пользователем:
class AnalogClockView: public Listener {
public:
   void update( ClockModel * model ) {
      ...   //отрисовка стрелочных часов
   }
   void userChangeTime( ClockModel * model ) {
      model->hour = ...
      model->minute = ...
      model->second = ...
   } 
};
Возникают две проблемы. Во-первых время можно поменять на любое, даже на некорректное. Во-вторых при изменении времени методом userChangeTime остальные наблюдатели, подключенные к модели, не узнают об этом, и измененное время отрисуется только на аналоговых часах, из которых был вызван метод userChangeTime.
Для решения этих двух проблем поля hour, minute и second сделаем закрытыми, и добавим в класс ClockModel сеттеры и геттеры для этих полей:
class ClockModel {
public:
   ...   
  
   void setHour( int h ) {
      if ( ( h >=0 ) && ( h < 24 ) ) { 
         hour = h;
         notifyAll(); //сообщаем всем о том, что время изменилось
      }
   }
   int getHour() {
      return hour;
   }
   
   ...
private:
   std::list < Listener * > listeners;
   int hour;
   int minute;
   int second;
   ...
};
Тогда функция userChangeTime будет выглядеть следующим образом:
void userChangeTime( ClockModel * model ) {
   model->setHour( ... );
   model->setMinute( ... );
   model->setSecond( ... );
} 
Однако, при изменении времени пользователем функция notifyAll() вызовется три раза подряд, что не очень хорошо. Чтобы избежать этого, вызов функции notifyAll можно убрать из сеттеров, саму функцию сделать публичной и вызвать её по завершению функции userChangeTime:
void userChangeTime( ClockModel * model ) {
   model->setHour( ... );
   model->setMinute( ... );
   model->setSecond( ... );
   model->notifyAll();
} 
Теперь вся ответственность за вызов метода notifyAll лежит на программисте, вызывающем сеттер, так как notifyAll не будет вызываться автоматически.
2.4. Проблема удаления слушателей. 
В контейнере listeners хранятся указатели на слушателей, а значит если какой-то из слушателей будет удален, указатель будет указывать на несуществующий объект, и при попытке вызвать у него метод update, может произойти ошибка. Чтобы избежать такого, добавление и удаление слушателей можно сделать автоматическим:
class Listener {
public:
   virtual void update( ClockModel * model ) = 0;
   Listener( ClockModel * m ) {
      model = m;
      model->addListener( this );
   }
   ~Listener() {
      model->removeListener( this );
   }
private:
   ClockModel * model;
};
Тогда для вывода на экран аналоговых и цифровых часов будет достаточно написать:
ClockModel model;
AnalogClockView aView( model );
DigitalClockView dView( model );
3. Прочие проблемы использования шаблона Listener. 
3.1. Проблема большого числа различных слушателей. 
 ассмотрим программу с графическим интерфейсом и большим числом различных элементов управления: кнопками, полосами прокрутки, текстовыми полями и т.д.  азные элементы должны реагировать на разные события, такие как нажатие клавиши, нажатие левой кнопки мышки, изменение позиции мышки и прочее. Использование шаблона, рассмотренного выше, будет неэффективно, так как при каком-либо событии функция notifyAll будет оповещать об изменении всех, в то числе и тех слушателей, на которых произошедшее событие сказаться не может. То есть, будет совершаться много лишней работы. Эту проблему можно решить двумя способами: 
- Завести несколько массивов для разных типов слушателей (используется в Java).
Тогда в классе модели будут различные методы для добавления и удаления различных слушателей. Например addMouseMoveListener, addMouseRightClickListener и т.д. 
- При добавлении слушателя, вторым параметром указывать его тип:
addListener( Listener * listener, ListenerType type );
Тип слушателей ListenerType удобней всего сделать перечислимым:
enum ListenerType { MouseMoveListener, MouseRightClickListener, ... };
В единственном массиве listener необходимо будет хранить указатель на слушателя и его тип. По типу слушателя можно будет сказать, необходимо ли вызывать у него update при данном событии или нет.  
3.2. Проблема большой модели. 
Пусть у нас есть большая и сложная модель, например модель компьютерной игры с картой. Тогда перерисовка всей карты методом update( Model* ) при каком-либо событии будет занимать значительное время. Чтобы ускорить процесс можно перерисовывать только видимую (активную) область карты, но для этого необходимо предавать методу update не только модель, но и например, границы активной области. То есть, при большой и сложной модели не всегда хорошо передавать её в методу update целиком.