Механизм сигналов-слотов

Для удобного решения некоторых классов задач необходимо иметь возможность выполнять вызов подпрограммы одного - не известного на момент компиляции - объекта из другого объекта. Механизм сигналов/слотов предоставляет удобную возможность осуществлять такие вызовы с использованием минимального объёма кода.

Суть механизма в том, что объект помимо обычных подпрограмм декларирует набор сигналов и/или слотов. К одному сигналу может быть подключено несколько слотов других объектов. Когда объекту необходимо произвести вызов подпрограмм других объектов он возбуждает сигнал. Предлагаемая библиотека самостоятельно производит вызов подключенных подпрограмм объектов.

Для примера рассмотрим типичный сценарий при программировании UI: отображение информации о приложении при нажатии кнопки. Приложение содержит объект типа Action, подпрограмма Output_About которого выводит необходимую информацию, а слот Output_About_Slot позволяет вызвать данную подпрограмму. Кроме этого приложение имеет объект Button, который возбуждает сигнал Clicked_Signal при нажатии на него пользователем.

Начнём с реализации кнопки.

private with Core.Connectables;
with Core.Slots_0;
private with Core.Slots_0.Emitters;

package Buttons is

   type Button is tagged limited private;

   not overriding function Clicked_Signal
    (Self : in out Button) return not null access Core.Slots_0.Signal'Class;

   procedure Click (Self : in out Button'Class);

private

   type Button is
     limited new Core.Connectables.Connectable_Object with
   record
      Clicked :
        aliased Core.Slots_0.Emitters.Emitter (Button'Unchecked_Access);
   end record;

end Buttons;

Функция Clicked_Signal возвращает сигнал кнопки, используемых для подключения слотов других объектов. Подпрограмма Click возбуждает этот сигнал. Тип Button имеет дополнительный элемент - эмиттер сигнала.

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

package body Buttons is

   -----------
   -- Click --
   -----------

   procedure Click (Self : in out Button'Class) is
   begin
      Self.Clicked.Emit;
   end Click;

   --------------------
   -- Clicked_Signal --
   --------------------

   not overriding function Clicked_Signal
    (Self : in out Button) return not null access Core.Slots_0.Signal'Class is
   begin
      return Self.Clicked'Unchecked_Access;
   end Clicked_Signal;

end Buttons;

В реализации подпрограммы Click выполняется вызов метода Emit у элемента Emitter. При выполнении этого метода происходит вызов подпрограмм подключенных слотов.

Пакет реализации вывода информации о приложении выглядит следующим образом:

private with Core.Connectables;
with Core.Slots_0;
private with Core.Connectables.Slots_0.Generic_Slots;

package Actions is

   type Action is tagged limited private;

   not overriding procedure Output_About (Self : in out Action);

   function Output_About_Slot
    (Self : in out Action'Class) return Core.Slots_0.Slot'Class;

private

   type Action is
     limited new Core.Connectables.Connectable_Object with null record;

   package Output_About_Slots is
     new Core.Connectables.Slots_0.Generic_Slots (Action, Output_About);

   function Output_About_Slot
    (Self : in out Action'Class) return Core.Slots_0.Slot'Class
       renames Output_About_Slots.To_Slot;

end Actions;

Подпрограмма Output_About выполняет вывод информации о приложении. Подпрограмма Output_About_Slot возвращает слот для выполнения подключения к сигналу. Настраиваемый пакет Output_About_Slots предоставляет реализацию последней подпрограммы и содержит код для обработки подключения и вызова указанной подпрограммы при возбуждении сигнала.

Реализации этого пакета выглядит следующим образом:

with Ada.Text_IO;

package body Actions is

   ------------------
   -- Output_About --
   ------------------

   not overriding procedure Output_About (Self : in out Action) is
   begin
      Ada.Text_IO.Put_Line ("Signal/Slot in Ada demo application");
   end Output_About;

end Actions;

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

with Actions;
with Buttons;

procedure Demo is
   A : Actions.Action;
   B : Buttons.Button;

begin
   B.Clicked_Signal.Connect (A.Output_About_Slot);
   B.Click;
end Demo;

В строке 9 производится соединение сигнала Signal_Clicked объекта B и слота Output_About_Slot объекта A.

Исходники библиотеки тут

http://forge.ada-ru.org/matreshka/browser/trunk/design/ui/source


Вадим Годунко, январь 2017г.