Chain of Responsibility


A set of objects are organized into a chain through which events can
propagate.  The object that receives an event can either handle the
event itself, or propagate it to the next object in the chain.

In this example we model application help.  If a widget receives a help
request that it can satisfy itself, it does so, providing some
context-sensitive help.  Otherwise, the widget just forwards the help
request to its parent.

Implementation

Help behavior is defined by a help handler type:

  package Help_Handlers is

     type Help_Handler_Type is
       abstract tagged limited private;
     ...

     procedure Set_Help_Topic
       (Handler : in out Help_Handler_Type;
        Topic   : in     Help_Topic);

     procedure Handle_Help
       (Handler : in Help_Handler_Type) is abstract;

  end Help_Handlers;

A help request is made by calling Handle_Help.  Types in the derivation
class override Handle_Help to provide help if a help topic is defined
for the object, and if not, to forward the request to the next help
handler.

Widget types use an access discriminant to connect a widget to its
parent, which is also the widget's help successor:

  package Widgets is

     type Root_Widget_Type is
       abstract new Help_Handler_Type with private;

     type Widget_Type (Parent : access Root_Widget_Type'Class) is
       abstract new Root_Widget_Type with private;
    ...
  end Widgets;


Widget types that derive from Widget_Type override the Help_Handler
operation to provide type-specific help:

  package Widgets.Buttons is

     type Button_Widget is new Widget_Type with null record;

     procedure Handle_Help (Widget : in Button_Widget);

  end Widgets.Buttons;


If the button can't handle the help request itself, it forwards it to
the next help handler in the chain, its parent:

   procedure Handle_Help (Widget : in Button_Widget) is
   begin
      if Has_Help (Widget) then
         <show button help>
      else
         Handle_Help (Widget.Parent.all);
      end if;
   end Handle_Help;


The dialog widget is a top-level widget, so it doesn't have a widget
parent to whom to forward a help request.  Instead, its successor is any
help handler:

  package Widgets.Dialogs is

     type Dialog_Widget (Handler : access Help_Handler_Type'Class) is
       new Root_Widget_Type with null record;

     procedure Handle_Help (Dialog : in Dialog_Widget);

  end Widgets.Dialogs;


The Application_Type derives from help handler, allowing it to handle
help requests forwarded by top-level widgets:

  package Applications is

     type Application_Type is new Help_Handler_Type with null record;

     procedure Handle_Help (Application : in Application_Type);

  end Applications;


An application object is the end of the chain.  It doesn't have a parent
and can't forward help requests any further, so it just displays general
help:

   procedure Handle_Help (Application : in Application_Type) is
   begin
     <display application help>
   end;


The test driver creates a widget hierarchy comprising a dialog widget
and a button widget, and binds it to an application:

  procedure Test_Help is

     Application : aliased Application_Type;

     Dialog : aliased Dialog_Widget (Application'Access);

     Button : aliased Button_Widget (Dialog'Access);

  begin

     Handle_Help (Button);
     ...
  end;

The help request is propagated up the chain, until a help handler to
actually service the request is found.

The following code is ready for GNAT Chop:

--STX
with Ada.Text_IO;  use Ada.Text_IO;

package body Applications is

   procedure Handle_Help (Application : in Application_Type) is
   begin
      Put_Line ("this is help for the application");
   end;

end Applications;
with Help_Handlers; use Help_Handlers;

package Applications is

   type Application_Type is new Help_Handler_Type with null record;

   procedure Handle_Help (Application : in Application_Type);

end Applications;
with Ada.Text_IO;  use Ada.Text_IO;

package body Help_Handlers is

   function Has_Help
     (Handler : Help_Handler_Type) return Boolean is
   begin
      return Handler.Topic /= No_Help_Topic;
   end;

   procedure Set_Help_Topic
     (Handler : in out Help_Handler_Type;
      Topic   : in     Help_Topic) is
   begin
      Handler.Topic := Topic;
   end;

end Help_Handlers;


with Help_Topics; use Help_Topics;

package Help_Handlers is

   pragma Elaborate_Body;


   type Help_Handler_Type is
     abstract tagged limited private;

   type Help_Handler_Access is
      access all Help_Handler_Type'Class;

   function Has_Help
     (Handler : Help_Handler_Type) return Boolean;

   procedure Set_Help_Topic
     (Handler : in out Help_Handler_Type;
      Topic   : in     Help_Topic);

   procedure Handle_Help
     (Handler : in Help_Handler_Type) is abstract;

private

   type Help_Handler_Type is
      abstract tagged limited record
         Topic : Help_Topic := No_Help_Topic;
      end record;

end Help_Handlers;


package Help_Topics is

   pragma Pure;

   type Help_Topic is new Natural;

   No_Help_Topic : constant Help_Topic := 0;

end Help_Topics;
with Ada.Text_IO; use Ada.Text_IO;

with Widgets.Dialogs;  use Widgets.Dialogs;
with Widgets.Buttons;  use Widgets.Buttons;
with Applications;     use Applications;
with Help_Topics;      use Help_Topics;

procedure Test_Help is

   Application : aliased Application_Type;

   Dialog : aliased Dialog_Widget (Application'Access);

   Button : aliased Button_Widget (Dialog'Access);

begin


   Handle_Help (Button);
   New_Line;

   Set_Help_Topic (Dialog, 1);
   Handle_Help (Button);
   New_Line;

   Set_Help_Topic (Button, 1);
   Handle_Help (Button);
   New_Line;

end Test_Help;
with Ada.Text_IO; use Ada.Text_IO;

package body Widgets.Buttons is

   procedure Handle_Help (Widget : in Button_Widget) is
   begin
      if Has_Help (Widget) then

         Put_Line ("this is help for buttons");

      else

         Put_Line ("button is forwarding help to its parent");
         Handle_Help (Widget.Parent.all);

      end if;
   end Handle_Help;

end Widgets.Buttons;

package Widgets.Buttons is

   type Button_Widget is new Widget_Type with null record;

   procedure Handle_Help (Widget : in Button_Widget);

end Widgets.Buttons;

with Ada.Text_IO;  use Ada.Text_IO;

package body Widgets.Dialogs is

   procedure Handle_Help
     (Dialog : in Dialog_Widget) is
   begin
      if Has_Help (Dialog) then

         Put_Line ("this is help for dialog");

      else

         Put_Line ("dialog is forwarding help request to successor");
         Handle_Help (Dialog.Handler.all);

      end if;
   end Handle_Help;

end Widgets.Dialogs;

package Widgets.Dialogs is

   type Dialog_Widget (Handler : access Help_Handler_Type'Class) is
     new Root_Widget_Type with null record;

   procedure Handle_Help (Dialog : in Dialog_Widget);

end Widgets.Dialogs;

with Help_Handlers; use Help_Handlers;

package Widgets is

   type Root_Widget_Type is
     abstract new Help_Handler_Type with private;

   type Widget_Type (Parent : access Root_Widget_Type'Class) is
     abstract new Root_Widget_Type with private;

private

   type Root_Widget_Type is
      abstract new Help_Handler_Type with null record;

   type Widget_Type (Parent : access Root_Widget_Type'Class) is
     abstract new Root_Widget_Type with null record;

end Widgets;

Contributed by: Matthew Heaney
Contributed on: May 24, 1999
License: Public Domain

Back