comp.lang.ada
 help / color / mirror / Atom feed
From: "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de>
Subject: Re: Ada 2005: Attempting to create a useful subject/observer pattern
Date: Wed, 25 Oct 2006 10:22:54 +0200
Date: 2006-10-25T10:22:54+02:00	[thread overview]
Message-ID: <s5ootn75t6n8.15f9aamwidryi$.dlg@40tude.net> (raw)
In-Reply-To: 1161723710.859323.228560@f16g2000cwb.googlegroups.com

On 24 Oct 2006 14:01:50 -0700, Lucretia wrote:

> Like the subject states, I'm trying to implement a usable
> subject/observer pattern which is an extension of the GoF original and
> something closer to Java's Listeners.

yuck...

> The idea was to use the code in a
> gui library. After talking on IRC (#Ada) and working with the guys
> there (Baldrick ;D), it was noted that GNAT is flawed in it's
> interfaces, but I'd like to know if anyone here can indicate whether
> the code is wrong and GNAT should accept it or not.

I cannot comment on compiler bugs, I am not a language lawyer. So I only
comment on to the code.

> I'm using FSF GNAT from GCC 4.1.1 and the errors are:
> 
> gnatmake -gnat05 test_observers2.adb
> gcc -c -gnat05 test_observers2.adb
> test_observers2.adb:16:34: (Ada 2005) interface subprogram "Update"
> must be abstract or null
> test_observers2.adb:23:31: expected private type "Event" defined at
> events.ads:5test_observers2.adb:23:31: found private type "Mouse_Event"
> defined at mouse_events.ads:5
> my_observer.ads:10:03: (Ada 2005) interface subprogram "On_Mouse_Click"
> must be abstract or null
> subjects.ads:15:10: (Ada 2005) interface subprogram "Update" must be
> abstract or null
> subjects.ads:15:20: operation can be dispatching in only one type
> gnatmake: "test_observers2.adb" compilation error
> 
> All code below is GPL'd and can be gnatchopped:
> 
> with Ada.Finalization;
> package Events is
>   type Event is new Ada.Finalization.Controlled with private;
> private
>   type Event is new Ada.Finalization.Controlled with null record;
> end Events;
>
> with Events; 
> package Mouse_Events is
>   type Mouse_Event is new Events.Event with private;
> private 
>   type Mouse_Event is new Events.Event with null record;
> end Mouse_Events;
>
> with Mouse_Events;
> with Observers; 
> package Mouse_Observers is
>   type Mouse_Observer is interface and Observers.Observer;
>   procedure On_Mouse_Click(Self : in Mouse_Observer; Event : in
> Mouse_Events.Mouse_Event) is null;

I suppose this should be:

procedure On_Mouse_Click
          (  Self  : in Mouse_Observer;
             Event : in Mouse_Events.Mouse_Event'Class
          )  is null;

> end Mouse_Observers;
>
> with Ada.TexT_IO; 
> package body My_Observer is
> --  procedure Update(Self : in My_Observer_Type) is
> --  begin
> --    Ada.TexT_IO.Put_Line("[My_Observer.Update]");
> --  end Update;
> 
>   procedure On_Mouse_Click(Self : in Mouse_Observer; Event : in
> Mouse_Events.Mouse_Event) is
>   begin
>     Ada.TexT_IO.Put_Line("[My_Observer.On_Mouse_Click]");
>   end On_Mouse_Click;
> end My_Observer;
>
> with Mouse_Observers;
> with Mouse_Events;
> package My_Observer is
>   type My_Observer_Type is new Mouse_Observers.Mouse_Observer with
> private; 
>   overriding
> --  procedure Update(Self : in My_Observer_Type);
>   procedure On_Mouse_Click(Self : in Mouse_Observers.Mouse_Observer;
> Event : in Mouse_Events.Mouse_Event);
> 
> private
>   type My_Observer_Type is new Mouse_Observers.Mouse_Observer with null
> record;
> end My_Observer;
>
> package Observers is 
>   type Observer is interface;

Why don't you define abstract Notify on Observer? Why Observer need to be
an interface? What does guarantee detachment of observers upon
finalization?

>   type Observer_Access is access all Observer'Class;
> end Observers;
>
> with Observers;
> 
> package body Subjects is
>   use type Observer_Lists.Cursor;
>   -- Add an observer to this subject's internal list.
>   procedure Attach(Self : in out Subject'Class; Observer : access
> Observers.Observer'Class) is
>   begin
>     Self.Observer_List.Append(New_Item =>
> Observers.Observer_Access(Observer));
>   end Attach;
> 
>   -- Remove an observer from this subject's internal list.
>   procedure Detach(Self : in out Subject'Class; Observer : access
> Observers.Observer'Class) is
> 
>     Position : Observer_Lists.Cursor :=
> Self.Observer_List.Find(Observers.Observer_Access(Observer));
>   begin
>     if Position /= Observer_Lists.No_Element then
>       Self.Observer_List.Delete(Position);
>     end if;
>   end Detach;
> 
>   -- Notify all observers who are monitoring this subject that
> something has happened.
>   procedure Notify(Self : in Subject; Event : in Events.Event) is
> 
>     Current : Observer_Lists.Cursor := Self.Observer_List.First;
>   begin
>     while Current /= Observer_Lists.No_Element loop
>       if Observer_Lists.Element(Current)'Class = Matching_Event'Class
> then

This should be something like:

if Observer_Lists.Element (Current) in Matching_Event'Class then

However, I would not use a design that queues events to [ultimate]
observers. Rather I would queue observers to the event sources. That would
save me event filtering issue, as well as crude emulations of multiple
dispatch like you tried above.

>         Update(Observer_Lists.Element(Current), Event);
>         --Observer_Lists.Element(Current).Update;
>         Current := Observer_Lists.Next(Current);
>       end if;
>     end loop;
>   end Notify;

There is a big problem with traversing the list of observers, while calling
notifications. If some of the notification causes a list change? This is a
no-no with Ada.Containers. So you have to undertake some actions to ensure
that this would never happen. Unfortunately it is necessary to happen. I
usually add some parameters to Update, to allow the subscribers to postpone
some notifications from the callback. But this is a complex issue for such
a small example.

> --  function Equals(Left, Right : access Observers.Observer) return
> Boolean is
>   function Equals(Left, Right : Observers.Observer_Access) return
> Boolean is
>     use type Observers.Observer_Access;
>   begin
>     if Left = Right then
>       return True;
>     end if;
>     return False;
>   end Equals;

My rule of thumb: If objects have referential identity => they have to be
limited. So I would make Observer Limited_Controlled, if I wished to
compare pointers to them.

> end Subjects;
> with Observers;
> with Ada.Containers.Doubly_Linked_Lists;
> with Ada.Finalization;
> with Events;
> 
> package Subjects is
> 
>   type Subject is new Ada.Finalization.Limited_Controlled with private;
> 
>   procedure Attach(Self : in out Subject'Class; Observer : access
> Observers.Observer'Class);
>   procedure Detach(Self : in out Subject'Class; Observer : access
> Observers.Observer'Class);
> 
>   generic
>     type Matching_Event is new Events.Event with private;
>     with procedure Update(Self : in Observers.Observer; Event : in
> Events.Event) is abstract;

   ... Matching_Event'Class, I suppose.

>   procedure Notify(Self : in Subject; Event : in Events.Event);

procedure Notify(Self : in Subject'Class; Event : in Events.Event'Class);

Another empirical rule: if a subprogram takes tagged arguments and is not
primitive (or maybe located in a body), then it should be class-wide.

And, well, generics to hide type casting ... 

> private
> 
>   function Equals(Left, Right : Observers.Observer_Access) return
> Boolean;
> 
>   package Observer_Lists is new
> Ada.Containers.Doubly_Linked_Lists(Observers.Observer_Access, Equals);
> 
>   type Subject is new Ada.Finalization.Limited_Controlled with
>     record
>       Observer_List : Observer_Lists.List;
>     end record;
> 
> end Subjects;
> with My_Observer;
> with Subjects;
> --with Observers;
> with Mouse_Events;
> 
> procedure Test_Observers2 is
> 
> --  Obs     : Observers.Observer_Access := new
> My_Observer.My_Observer_Type;
> --  Obs     : access Observers.Observer := new
> My_Observer.My_Observer_Type;
>   Obs         : aliased My_Observer.My_Observer_Type;
>   Subject     : Subjects.Subject;
>   Mouse_Event : Mouse_Events.Mouse_Event;
> 
>   procedure Notify_Mouse_Click is new Subjects.Notify(
>     Matching_Event => Mouse_Events.Mouse_Event,
>     Update         => My_Observer.On_Mouse_Click);
> 
> begin
>   Subject.Attach(Obs'Access);
> --  Subject.Attach(Observers.Observer(Obs)'Access);
> --  Subject.Notify;
>   Notify_Mouse_Click(Subject, Mouse_Event);
>   
> end Test_Observers2;

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de



  parent reply	other threads:[~2006-10-25  8:22 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-10-24 21:01 Ada 2005: Attempting to create a useful subject/observer pattern Lucretia
2006-10-24 21:08 ` Lucretia
2006-10-25  2:01 ` Adam Beneschan
2006-10-25  8:22 ` Dmitry A. Kazakov [this message]
2006-10-25 16:29 ` Matthew Heaney
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox