wiki:ru/signals

Version 2 (modified by vadim.godunko, 10 years ago) ( diff )

--

Механизм Signal-Slot

Приятная и полезная концепция, приходящая на замену классической концепции подпрограмм обратного вызова. Основные отличия:

  • сигналы и слоты есть части типа, наравне с полями записи и примитивными подпрограммами;
  • отсуствует понятие "данных пользователя", задаваемых при выполнении соединения;
  • неизвестен (теоретически) объект-отправитель;
  • может использоваться в для межзадачного взаимодействия в событийно управляемых программах.

Не все перечисленные выше требования легко решить средствами Ada.

Вариант 1

API библиотеки поддержки

Библиотека поддержки состоит из нескольких основных пользовательских пакетов. Корневой пакет определяет базовый тип, способный взаимодействовать с механизмом сигналов/слотов. Все пользовательские типы должны быть порождены от него.

package Objects is

   type Abstract_Object is
     abstract new Ada.Finalization.Limited_Controlled with private;

private
   ...
end Objects;

Дочерние пакеты предоставляют возможность создания сигналов с определённым набором параметров и типом параметров. Они выполнены в виде настраиваемых пакетов, за исключением одного, определяющего сигналы без параметров:

package Objects.Signals is

   ----------------------
   -- Signal_Connector --
   ----------------------

   type Signal_Connector (<>) is limited private;

   generic
      type T is abstract new Abstract_Object with private;

   package Generic_Connectors is

      procedure Connect
       (Self   : Signal_Connector;
        Object : not null access T'Class;
        Method : not null access procedure (Self : not null access T));

   end Generic_Connectors;

   ------------
   -- Signal --
   ------------

   type Signal
    (Object : not null access Abstract_Object'Class) is tagged limited private;

   not overriding procedure Emit (Self : Signal);

   not overriding function Connector
    (Self : in out Signal) return Signal_Connector;

private
   ...
end Objects.Signals;

Тип Signal_Connector предназначен для выполнения операций подключения/отключения соединений. Тип Signal предназначен для объявления поля записи, выполяющего обработку сигналов.

Настраиваемый пакет Generic_Connectors предназначен для настройки на конкретный тип подключаемого объекта и содержит подпрограммы управления соединениями.

Пример использования

В качестве примера определим два тэговых типа Emitter и Collector, первый будет возбуждать сигнал, а второй его получать.

Код спецификации и реализации первого приведён ниже.

with Objects.Signals;

package Emitters is

   type Emitter is new Objects.Abstract_Object with private;

   function Signal
    (Self : not null access Emitter) return Objects.Signals.Signal_Connector;

   procedure Test (Self : not null access Emitter);

private

   type Emitter is new Objects.Abstract_Object with record
      Signal : aliased Objects.Signals.Signal (Emitter'Access);
   end record;

end Emitters;
package body Emitters is

   ------------
   -- Signal --
   ------------

   function Signal
    (Self : not null access Emitter) return Objects.Signals.Signal_Connector is
   begin
      return Self.Signal.Connector;
   end Signal;

   ----------
   -- Test --
   ----------

   procedure Test (Self : not null access Emitter) is
   begin
      Self.Signal.Emit;
   end Test;

end Emitters;

Для типа объявлена подпрограмма Signal, возвращающая Signal_Connector и позволяющая выполнять операции подключения/отключения.

Подпрограмма Test предназначена для демонстрации работы механизма и просто выполняет возбуждение сигнала.

Тип Collector, предназначенный для обработки полученного сигнала выглядит следующим образом:

with Objects;

package Collectors is

   type Collector is new Objects.Abstract_Object with private;

   procedure Process (Self : not null access Collector);

private

   type Collector is new Objects.Abstract_Object with null record;

end Collectors;
with Ada.Text_IO;

package body Collectors is

   procedure Process (Self : not null access Collector) is
   begin
      Ada.Text_IO.Put_Line ("Processing");
   end Process;

end Collectors;

Для выполнения подключения необходимо так же выполнить настройку пакета Generic_Connectors:

with Objects.Signals;

package Collectors.Connections is
  new Objects.Signals.Generic_Connectors (Collector);

Главная подпрограмм выглядит следующим образом:

with Collectors.Connections;
with Emitters;

procedure Main is
   C : aliased Collectors.Collector;
   E : aliased Emitters.Emitter;

begin
   Collectors.Connections.Connect
    (E.Signal, C'Access, Collectors.Process'Access);
   E.Test;
end Main;

Выводы

Выглядит вполне приемлемо. Однако, слишком много необходимо сделать для выполнения подключения. Одна из возможных идей для рассмотрения - настройка специального пакета, принимающего тип и его подпрограмму, а уже потом использование подпрограммы Connect из него.

Вариант 1.1

Этот вариант является модификацией предыдущего в части объявления и подключения слотов. Настраиваемый пакет Generic_Slot в Objects.Signals настраивается на тэговый тип и подпрограмму, играющую роль слота и предоставляет подпрогаммы подключения/отключения:

API поддержки

package Objects.Signals is

   ----------------------
   -- Signal_Connector --
   ----------------------

   type Signal_Connector (<>) is limited private;

   generic
      type T is abstract new Abstract_Object with private;
      with procedure Method (Self : not null access T) is abstract;

   package Generic_Slot is

      procedure Connect
       (Self   : Signal_Connector;
        Object : not null access T'Class);

   end Generic_Slot;

   ------------
   -- Signal --
   ------------

   type Signal
    (Object : not null access Abstract_Object'Class) is tagged limited private;

   not overriding procedure Emit (Self : Signal);

   not overriding function Connector
    (Self : in out Signal) return Signal_Connector;

private
   ...
end Objects.Signals;

Пример использования

По сравнения с предыдущим вариантом изменяется способ использования только для типов, содержащих слоты. Для каждого слота объявляется собственный дочерний пакет, содержащий настройку пакета Objects.Signals.Generic_Slot:

with Objects.Signals;

package Collectors.Process_Slot is
  new Objects.Signals.Generic_Slots (Collector, Process);

Главная программа выглядит следующим образом:

with Collectors.Process_Slot;
with Emitters;

procedure Main is
   C : aliased Collectors.Collector;
   E : aliased Emitters.Emitter;

begin
   Collectors.Process_Slot.Connect (E.Signal, C'Access);
   E.Test;
end Main;

Выводы

Больше писанины, поскольку необходимо объявлять отдельный пакет для каждого слота, но (1) меньше усилий на выполнение подключения и лучше понимание происходящего, и (2) жестко контролируется, что слот есть примитивная операция типа.

Note: See TracWiki for help on using the wiki.