Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Универсальная подписка  (Прочитано 6947 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« : 31-05-2006 08:03 » 

Как известно, в языках .NET реализован универсальный механизм подписки на события. Пример кода:
Код: (Text)
using System;
namespace Test
{
  // Издатель.
  class Publisher
  {
    // Делегат обработки событий.
    public delegate void Delegate(Publisher publisher);
    // События.
    public event Delegate EventA;
    public event Delegate EventB;
    // Внутренние обработчики событий издателя.
    private void OnEventA()
    {
      if(this.EventA != null) this.EventA(this);
    }
    private void OnEventB()
    {
      if(this.EventB != null) this.EventB(this);
    }
    // Полезная работа издателя, в результате которой
    // порождаются события.
    public void Test()
    {
      this.OnEventA();
      this.OnEventB();
    }
  }
  // Подписчик.
  class Subscriber
  {
    // Идентификатор объекта подписчика.
    private string id;
    // Конструктор.
    public Subscriber(Publisher publisher, string id)
    {
      this.id = id;
      // Подписка методов подписчика на события издателя.
      publisher.EventA += new Publisher.Delegate(this.HandlerA);
      publisher.EventB += new Publisher.Delegate(this.HandlerB);
    }
    // Обработчики уведомлений издателя о его событиях.
    private void HandlerA(Publisher publisher)
    {
      Console.WriteLine(string.Format("A handler of {0}.", this.id));
    }
    private void HandlerB(Publisher publisher)
    {
      Console.WriteLine(string.Format("B handler of {0}.", this.id));
    }
  }
  // Тестовая программа.
  class Program
  {
    // Основная программа.
    [STAThread]
    static void Main(string[] args)
    {
      // Создание издателя.
      Publisher publisher = new Publisher();
      // Создание множества подписчиков.
      Subscriber[] subscribers = new Subscriber[]
      {
        new Subscriber(publisher, "1"),
        new Subscriber(publisher, "2")
      };
      // Выполнение полезной работы издателя - подписчики
      // автоматически получают уведомления о событиях.
      publisher.Test();
      // Окно консоли закроется после нажатия Enter.
      Console.ReadLine();
    }
  }
}
Механизм красивый, элегантный. В чистом C++ ничего подобного не имеется. Благодаря этому механизму можно подписывать любые соответствующие определяемому делегатом прототипу методы любых объектов к любому соответствующему делегату событию любого объекта. Т.е. делегат вообще является связующим звеном всей конструкции, определяя универсальный функтор, в который можно запаковывать вызовы любых функций и методов и подписывать списки эти функторов на вызов по событию; а конкретный делегат определяет конкретный прототип метода.

Давайте подумаем, как можно в C++ реализовать столь же простой для использования механизм универсальной подписки на события.

Книжка "Паттерны проектирования" даёт нам паттерны Command и Observer. Их недостатком является отсутствие должной гибкости: нужно использовать наследование, т.е. для каждого события реализовывать абстрактный класс этого события; в каждую реализацию события приходится волочить механизм обработки списка подписчиков.

Книжка "Современное проектирование на C++" в главе 5 содержит описание универсальных функторов, позволяющих решить задачу. Но и в этом случае имеется ряд недостатков: невозможность гибко управлять параметрами обработчика события, что приводит к появлению 16 перегруженных методов с количеством параметров от 0 до 15; целый ворох шаблонных классов, который наводит на мысль воспользоваться библиотекой Loki, вместо того, чтобы реализовывать всё самостоятельно.

Можно ли проще? Какие решения обеспечивают баланс достаточной универсальности и очевидной простоты?
« Последнее редактирование: 31-05-2006 08:06 от dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Serega
Гость
« Ответ #1 : 31-05-2006 14:45 » 

Можно попробовать, это не так сложно, только пока что у меня не рождается решения без макросов или набора перегруженных фукций, если сильно надо дерни меня на выходных или вечерами - придумаем.

Проще всего меня днем в аське найти, но днем я врядли смогу помочь, а вечером лучше Хоху растормоши чтобы он напомнил, а то я обычно все забываю под вечер Улыбаюсь
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 31-05-2006 16:32 » new

Цитата: Serega
если сильно надо
Нет, это вопрос чисто умозрительный и совершенно не срочный. Улыбаюсь Я предложил подумать.

Можно ввести одно ограничение, не вызывающее больших проблем у пользователей, значительно упрощающее задачу: в .NET принято (но не обязательно) прототипы делегатов и обработчиков задавать следующим образом:
Код: (Text)
void Handler(object sender, EventArgs arg)
Во-первых, обработчик ничего не возвращает, так как внутри коллекции обработчиков события эти результаты никак не выудить. Во-вторых, у него два аргумента: объект-издатель, посылающий уведомление, и объект класса EventArgs, в который упаковывается вся прочая информация. В .NET рекомендуется, чтобы классы-аргументов обработчиков были производными от EventArgs (хотя тоже не обязательно).

В C++ для полной гибкости следовало бы определить функцию или метод типа:
Код: (Text) С++
void (*)(void *sender, void *arg)
Однако такое решение полностью лишает нас возможности контролировать типы параметров обработчика. Все проблемы крупной программы перекладывают с "плеч" компилятора на плечи программиста. Такое решение + потенциальный источник ошибок времени исполнения.

Можно было бы воспользоваться шаблонами, но здесь возникают проблемы с определением "делегата". Конструкции вида
Код: (C++)
template<class Publisher, class EventArg>
typedef void (*Delegate)(Publisher *sender, EventArg *arg);
запрещены. Применение шаблонов к typedef появится только в будущем стандарте языка (это уже запланировано).

С другой стороны, вышеприведённое определение применимо лишь к функциям, для методов классов необходимо иное:
Код: (C++)
void (Class::*Delegate)(Publisher *sender, EventArg *arg)

В любом случае определённость хотя бы с количеством параметров и семантическая определённость их типов (каков смысл типов параметров) снимает проблему создания множества перегруженных операторов для произвольных наборов параметров.
« Последнее редактирование: 31-05-2006 16:36 от dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines