Abstract Factories Revisited


In this article I discuss an alternate version of the abstract factory,
implemented using static polymorphism.

The example also uses the smart pointer pattern to take care of memory
management, and declares a singleton instance of an abstract data type.


Discussion

Way back when I showed how to implement the abstract factory pattern in
Ada95, using an example that more or less followed the one in the GoF
book.

In that version, the abstract factory is implemented as a class.  You
decide which kind of factory you want, and declare an instance of that
specific type, which gets elaborated during program initialization.

Ed Colbert gave me the idea that the abstract factory could be
implemented as a library-level renaming of another package.  What a
great idea!  Static polymorphism without it being a generic.

In this alternate version of the abstract factory pattern, I got rid of
the factory types, and just implemented factories as packages with an
identical interface.  The actual factory that is used as _the_ factory
is chosen by way of a library-level renaming.

If you recall, the example from the book was a maze game, in which you
enter rooms, doors, and walls.  Another version of the game features
"enchanted" maze items, and you select which version of the game you
want by choosing a different factory.

What we do here is first declare a family of maze item types, then
create a (singleton) maze object, using the factory to select the maze
items.  You get a maze whose behavior changes based on which factory you
use.


Implementation

When implementing a hierarchy of tagged types, I usually do the
following things:

o implement the root type as indefinite and limited

o have each derived type in the class export a constructor that returns
a (smart) pointer to that specific type

o have the primitive operations of the type take access parameters, so
that there's no need to explicitly dereference the return value of the
constructor (an access object)

o declare a primitive operation that's private (not visible to clients
outside of the package hierarchy), to deallocate instances of the type

Making the type limited and indefinite prevents clients from creating
instances themselves.  We want to force them to draw new instances from
a pool maintained by the package, by calling a constructor for the type.

The constructor returns a smart pointer, which relieves clients from the
burden of having to manually reclaim instances of the type.  This
eliminates memory leaks and dangling references.

We use this idiom for maze doors, walls, and rooms.  Package Mazes forms
the root of the subsystem, which is where we declare the root type:

package Mazes is

   type Maze_Item (<>) is abstract tagged limited private;
...
end Mazes;

There are two primitive operations for the type, and both take access
parameters (though for different reasons).  First, you can enter a maze
item:

   procedure Enter (Item : access Maze_Item) is abstract;

which in our example just prints out a message about what kind of maze
item you've entered.

Second, when there are no more references to an object, the smart
pointer automatically returns the item to storage by calling Free:

   procedure Free (Item : access Maze_Item);

In order to prevent clients from calling Free directly, we declare it in
the private part of the spec.  Note that we can't make it abstract,
because abstract operations must be publicly declared, so we provide a
default implementation that does nothing.

That last thing of interest in Mazes is the declaration of the smart
pointer:

   type Maze_Handle is private;

   function "+" (Handle : Maze_Handle) return Maze_Item_Access;

The identity operator "+" is used to convert from a handle to an access
object with only a small amount of syntactic overhead.  The conversion
is necessary because the primitive operations take Maze_Item as a
parameter, not Maze_Handle.

Note that there's nothing that prevents a client from making a copy of
the access object returned by the selector.  We just depend on Good Will
to always "dereference" a handle, never an access object:

  declare
    Wall : constant Maze_Handle := New_Wall;
  begin
    ... +Wall ...


(There is talk about extending the language to allow access types to be
limited, which would solve this potential problem.  For now, you're
going to have to promise me you'll be good, and only copy handles.)

We were lucky when we implemented Bool_Exps using smart pointers,
because the only interesting operations were operations that applied to
the entire class (were primitive for the root type).

Here, things get a little tricky, because Room and Door items declare
their own primitive operations.  In order to call a Room operation, we
need a Room view of the object, but "+" for Handle only returns a
Maze_Item view.  What do we do?

There are also times when we want the handle we've got to point to a
specific type.  For example, a Door can only be attached to Rooms.  If
we always manipulate Maze_Handles, we lose the ability to catch some
type mismatch errors statically.

Other times, we really don't care what kind of maze item we have.  A
Room, for example, doesn't care what it's attached to (it can be a door,
a wall, or even another room).  So we seem to be at an impasse.

However, I was able to solve these problems by declaring a different
handle type for each specific type in the hierarchy, and including an
operation to convert from the specific handle to the general handle.

The handle also provides a selector to return the specific view of the
maze item, allowing you to call primitive operations of the specific
type without any messy down-casts.

The Rooms package looks like this:

package Mazes.Rooms is

   type Maze_Room is new Maze_Item with private;

   


   type Room_Handle is private;

   function New_Room return Room_Handle;

   function "+"
     (Handle : Room_Handle) return Maze_Room_Access;

   function "+"
     (Handle : Room_Handle) return Maze_Handle;

   ...
end Mazes.Rooms;


The Room_Handle thus provides a static guarantee to a client (like Door)
that his handle is pointing at a Room, not some other kind of maze item:

  declare
     Room : constant Room_Handle := New_Room;
     Door : constant Door_Handle := New_Door;
  begin
     Set_Number (+Room, Number => 13);

     ... Get_Side (+Room, North) ...

     Set_Room (+Door, Front, Room);
  end;


Doors and Walls are implemented similarly.

Enchanted maze items derive from their unenchanted counterparts, and
just override the Enter operation to print out a different message.  (Of
course, they also provide their own constructor too.)

Armed with all these maze items, now it's time to actually build a maze.

Knowing that there's only going to be one maze, we can apply the
singleton pattern to ensure that only one maze gets created.  We make
the type limited and indefinite, and provide a "constructor" to return a
pointer to a statically allocated instance in the body.  (This is
similar to how the Fly-weight pattern is implemented).

I declared Maze_Type in its own child package, mostly so I could
localize a dependency on Maze_Rooms.  (The alternative --declaring
Maze_Type in the root package-- would require declaring Maze_Room there
too, which I found aesthetically unpleasing.)

The spec for Maze_Type looks like this:

package Mazes.Games is

   type Maze_Type (<>) is limited private;

   


   type Maze_Access is access all Maze_Type;

   function Maze return Maze_Access;
...
end Mazes.Games;


The singleton is statically declared in the body:

package body Mazes.Games is

   Singleton : aliased Maze_Type;

   ...

   function Maze return Maze_Access is
   begin
      return Singleton'Access;
   end;

   ...

end Mazes.Games;


Clients refer to the maze indirectly, by calling a selector that returns
a reference to the singleton.  Despite this, Maze manipulation has a
very natural syntax, because operations take access parameters:

   Room : constant Room_Handle :=
     Get_Room (1, Of_Maze => Maze);  <-- Maze returns ptr to singleton


The maze gets initialized during elaboration of the package body, by
populating the maze with a couple of rooms connected by a door:

with Mazes.Factory;
pragma Elaborate_All (Mazes.Factory);

package body Mazes.Games is

   Singleton : aliased Maze_Type;
   ...
begin

   declare

      Room_1 : constant Room_Handle := New_Room (1);

      Room_2 : constant Room_Handle := New_Room (2);

      Door : constant Door_Handle := New_Door (Room_1, Room_2);

   begin

      Singleton.Rooms := (Room_1, Room_2);

      Set_Side (+Room_1, North, New_Wall);
      ...

   end;

end Mazes.Games;


What we want to do is make the algorithm for maze initialization
independent of specific maze items.  That's where the factory comes in.

The Game package uses constructors for rooms, doors, and walls provided
by the factory, but is otherwise indifferent.  The kind of wall or the
kind of door used to construct the maze is a detail hidden by the
factory.

The spec for our "virtual" factory looks like this:

package  is

   function New_Wall return Maze_Handle;

   function New_Room
     (Number : Positive) return Room_Handle;

   function New_Door
     (Front_Room : Room_Handle;
      Back_Room  : Room_Handle) return Door_Handle;


In our example, there are two reifications of this virtual factory, a
default one for unenchanted maze items, and another for enchanted maze
items.  These are two different packages, each with an identical
interface:

package Mazes.Default_Factory is

   function New_Wall return Maze_Handle;
... (as above)


package Mazes.Enchanted_Factory is

   function New_Wall return Maze_Handle;
... (as above)



The body of the default factory returns unenchanted maze items:

   function New_Room
     (Number : Positive) return Room_Handle is

      Room : constant Room_Handle :=
        New_Room;  <-- constructor for unenchanted room
   begin
      Set_Number (+Room, Number);
      return Room;
   end;



The body of the enchanted factory returns enchanted maze items:

   function New_Room
     (Number : Positive) return Room_Handle is

      Room : constant Room_Handle :=
        New_Enchanted_Room;  <-- constructor for enchanted room
   begin
      Set_Number (+Room, Number);
      return Room;
   end;


The one thing that remains is to explain how to "connect" the maze to
the factory.  Recall that the body for the singleton was declared this
way:

with Mazes.Factory;
pragma Elaborate_All (Mazes.Factory);

package body Mazes.Games is ...;


Package Mazes.Factory is the actual "abstract factory" to which this
pattern refers.  It is implemented as a library-level renaming of either
the default factory or the enchanted factory.

If you implement Factory this way:

with Mazes.Default_Factory;
package Mazes.Factory renames Mazes.Default_Factory;

then you'll get the following output:

$ maze_main
entered room
entered wall
entered wall
entered door
entered wall


If you then implement Factory this other way:

with Mazes.Enchanted_Factory;
package Mazes.Factory renames Mazes.Enchanted_Factory;

then you'll get this other output:

$ maze_main
entered enchanted room - scary!
entered enchanted wall - by walking through it!
entered enchanted wall - by walking through it!
entered door needing spell - magic!
entered enchanted wall - by walking through it!


So you get a completely different behavior, by changing only a couple
lines of code.

As an example of this idea, you could use a factory to implement a
platform-neutral windowing API.  When you port your application to
another operating system, all you need to do is compile against a
different factory.  Your code is otherwise unchanged.

Matt


The code below is in a format suitable for use with gnatchop.

--STX
with Mazes;       use Mazes;
with Mazes.Games; use Mazes.Games;
with Mazes.Rooms; use Mazes.Rooms;

procedure Maze_Main is

   Room : constant Room_Handle :=
     Get_Room (1, Of_Maze => Maze);

begin

   Enter (+Room);

   for Direction in Direction_Type loop
      Enter (+Get_Side (+Room, Direction));
   end loop;

end;
with Mazes.Walls; use Mazes.Walls;

package body Mazes.Default_Factory is

   function New_Wall return Maze_Handle renames Walls.New_Wall;


   function New_Room
     (Number : Positive) return Room_Handle is

      Room : constant Room_Handle := New_Room;
   begin
      Set_Number (+Room, Number);
      return Room;
   end;


   function New_Door
     (Front_Room : Room_Handle;
      Back_Room  : Room_Handle) return Door_Handle is

      Door : constant Door_Handle := Doors.New_Door;
   begin
      Set_Room (+Door, Front, Front_Room);
      Set_Room (+Door, Back, Back_Room);

      return Door;
   end;

end Mazes.Default_Factory;
with Mazes.Rooms; use Mazes.Rooms;
with Mazes.Doors; use Mazes.Doors;

package Mazes.Default_Factory is

   function New_Wall return Maze_Handle;

   function New_Room
     (Number : Positive) return Room_Handle;

   function New_Door
     (Front_Room : Room_Handle;
      Back_Room  : Room_Handle) return Door_Handle;

end Mazes.Default_Factory;
with Mazes.Storage;
with Ada.Text_IO;  use Ada.Text_IO;

package body Mazes.Doors.Enchanted is

   type Door_Needing_Spell_Access is
     access all Door_Needing_Spell'Class;


   package Door_Storage is
     new Storage.Generic_Item
     (Door_Needing_Spell,
      Door_Needing_Spell_Access,
      Door_Handle);

   procedure Enter
     (Door : access Door_Needing_Spell) is
   begin
      Put_Line ("entered door needing spell - magic!");
   end;

   function New_Door_Needing_Spell return Door_Handle renames
     Door_Storage.New_Item;

   procedure Free (Door : access Door_Needing_Spell) renames
     Door_Storage.Do_Free;

end Mazes.Doors.Enchanted;
package Mazes.Doors.Enchanted is

   type Door_Needing_Spell is
     new Maze_Door with private;


   procedure Enter
     (Door : access Door_Needing_Spell);

   function New_Door_Needing_Spell return Door_Handle;

private

   type Door_Needing_Spell is
     new Maze_Door with null record;

   procedure Free (Door : access Door_Needing_Spell);

end Mazes.Doors.Enchanted;
with Mazes.Storage;
with Ada.Text_IO;

package body Mazes.Doors is

   package Door_Storage is
     new Storage.Generic_Item
     (Maze_Door,
      Maze_Door_Access,
      Door_Handle);


   procedure Enter (Door : access Maze_Door) is
   begin
      Ada.Text_IO.Put_Line ("entered door");
   end;


   procedure Set_Room
     (Door : access Maze_Door;
      Id   : in     Room_Id;
      Room : in     Room_Handle) is
   begin
      Door.Rooms (Id) := Room;
   end;


   function Get_Room
     (Door : Maze_Door;
      Id   : Room_Id) return Room_Handle is
   begin
      return Door.Rooms (Id);
   end;


   procedure Free (Door : access Maze_Door) renames
     Door_Storage.Do_Free;

   function New_Door return Door_Handle renames
     Door_Storage.New_Item;

   function "+"
     (Handle : Door_Handle) return Maze_Door_Access renames
     Door_Storage.Ref;

   function "+"
     (Handle : Door_Handle) return Maze_Handle is
   begin
      return Maze_Handle (Handle);
   end;

end Mazes.Doors;
with Mazes.Rooms; use Mazes.Rooms;

package Mazes.Doors is

   type Maze_Door is new Maze_Item with private;

   type Maze_Door_Access is access all Maze_Door'Class;


   type Room_Id is (Front, Back);


   procedure Enter (Door : access Maze_Door);

   procedure Set_Room
     (Door : access Maze_Door;
      Id   : in     Room_Id;
      Room : in     Room_Handle);

   function Get_Room
     (Door : Maze_Door;
      Id   : Room_Id) return Room_Handle;


   type Door_Handle is private;

   function New_Door return Door_Handle;

   function "+"
     (Handle : Door_Handle) return Maze_Door_Access;

   function "+"
     (Handle : Door_Handle) return Maze_Handle;

private

   type Room_Array is
      array (Room_Id) of Room_Handle;

   type Maze_Door is
      new Maze_Item with record
         Rooms   : Room_Array;
         Is_Open : Boolean := False;
      end record;

   procedure Free (Door : access Maze_Door);


   type Door_Handle is new Maze_Handle with null record;

end Mazes.Doors;
with Mazes.Rooms.Enchanted; use Mazes.Rooms.Enchanted;
with Mazes.Doors.Enchanted; use Mazes.Doors.Enchanted;
with Mazes.Walls.Enchanted; use Mazes.Walls.Enchanted;

package body Mazes.Enchanted_Factory is

   function New_Wall return Maze_Handle renames
     New_Enchanted_Wall;


   function New_Room
     (Number : Positive) return Room_Handle is

      Room : constant Room_Handle :=
        New_Enchanted_Room;
   begin
      Set_Number (+Room, Number);
      return Room;
   end;


   function New_Door
     (Front_Room : Room_Handle;
      Back_Room  : Room_Handle) return Door_Handle is

      Door : constant Door_Handle :=
        New_Door_Needing_Spell;
   begin
      Set_Room (+Door, Front, Front_Room);
      Set_Room (+Door, Back, Back_Room);

      return Door;
   end;

end Mazes.Enchanted_Factory;
with Mazes.Rooms; use Mazes.Rooms;
with Mazes.Doors; use Mazes.Doors;

package Mazes.Enchanted_Factory is

   function New_Wall return Maze_Handle;

   function New_Room
     (Number : Positive) return Room_Handle;

   function New_Door
     (Front_Room : Room_Handle;
      Back_Room  : Room_Handle) return Door_Handle;

end Mazes.Enchanted_Factory;


with Mazes.Enchanted_Factory;
--with Mazes.Default_Factory;
package Mazes.Factory renames
--  Mazes.Default_Factory;
  Mazes.Enchanted_Factory;




with Mazes.Doors;    use Mazes.Doors;
with Mazes.Factory;  use Mazes.Factory;
pragma Elaborate_All (Mazes.Factory);

package body Mazes.Games is

   Singleton : aliased Maze_Type;


   function Get_Room
     (Number  : in     Positive;
      Of_Maze : access Maze_Type) return Room_Handle is
   begin
      return Of_Maze.Rooms (Number);
   end;


   function Maze return Maze_Access is
   begin
      return Singleton'Access;
   end;


begin

   declare

      Room_1 : constant Room_Handle := New_Room (1);

      Room_2 : constant Room_Handle := New_Room (2);

      Door : constant Door_Handle := New_Door (Room_1, Room_2);

   begin

      Singleton.Rooms := (Room_1, Room_2);

      Set_Side (+Room_1, North, New_Wall);
      Set_Side (+Room_1, East, +Door);
      Set_Side (+Room_1, South, New_Wall);
      Set_Side (+Room_1, West, New_Wall);

      Set_Side (+Room_2, North, New_Wall);
      Set_Side (+Room_2, East, New_Wall);
      Set_Side (+Room_2, South, New_Wall);
      Set_Side (+Room_2, West, +Door);

   end;

end Mazes.Games;
with Mazes.Rooms;  use Mazes.Rooms;

package Mazes.Games is

   type Maze_Type (<>) is limited private;

   function Get_Room
     (Number  : in     Positive;
      Of_Maze : access Maze_Type) return Room_Handle;


   type Maze_Access is access all Maze_Type;

   function Maze return Maze_Access;

private

   type Room_Array is
      array (Positive range <>) of Room_Handle;

   type Maze_Type is
      limited record
         Rooms : Room_Array (1 .. 2);
      end record;

end Mazes.Games;
with Mazes.Storage;
with Ada.Text_IO;  use Ada.Text_IO;

package body Mazes.Rooms.Enchanted is

   type Enchanted_Room_Access is
      access all Enchanted_Room'Class;

   package Room_Storage is
     new Storage.Generic_Item
     (Enchanted_Room,
      Enchanted_Room_Access,
      Room_Handle);

   procedure Enter
     (Room : access Enchanted_Room) is
   begin
      Put_Line ("entered enchanted room - scary!");
   end;

   procedure Free (Room : access Enchanted_Room) renames
     Room_Storage.Do_Free;

   function New_Enchanted_Room return Room_Handle renames
     Room_Storage.New_Item;

end Mazes.Rooms.Enchanted;
package Mazes.Rooms.Enchanted is

   type Enchanted_Room is
     new Maze_Room with private;

   procedure Enter
     (Room : access Enchanted_Room);

   function New_Enchanted_Room return Room_Handle;

private

   type Enchanted_Room is
     new Maze_Room with null record;

   procedure Free (Room : access Enchanted_Room);

end Mazes.Rooms.Enchanted;
with Mazes.Storage;
with Ada.Text_IO;

package body Mazes.Rooms is

   package Room_Storage is
     new Storage.Generic_Item
     (Maze_Room,
      Maze_Room_Access,
      Room_Handle);


   procedure Enter (Room : access Maze_Room) is
   begin
      Ada.Text_IO.Put_Line ("entered room");
   end;


   procedure Set_Number
     (Room   : access Maze_Room;
      Number : in     Positive) is
   begin
      Room.Number := Number;
   end;


   function Get_Number
     (Room : access Maze_Room) return Positive is
   begin
      return Room.Number;
   end;


   procedure Set_Side
     (Room      : access Maze_Room;
      Direction : in     Direction_Type;
      Side      : in     Maze_Handle) is
   begin
      Room.Sides (Direction) := Side;
   end;


   function Get_Side
     (Room      : access Maze_Room;
      Direction : in     Direction_Type) return Maze_Handle is
   begin
      return Room.Sides (Direction);
   end;


   procedure Free (Room : access Maze_Room) renames
     Room_Storage.Do_Free;

   function New_Room return Room_Handle renames
     Room_Storage.New_Item;

   function "+"
     (Handle : Room_Handle) return Maze_Room_Access renames
     Room_Storage.Ref;

   function "+"
     (Handle : Room_Handle) return Maze_Handle is
   begin
      return Maze_Handle (Handle);
   end;

end Mazes.Rooms;
package Mazes.Rooms is

   type Maze_Room is new Maze_Item with private;

   type Maze_Room_Access is access all Maze_Room'Class;


   procedure Enter (Room : access Maze_Room);

   procedure Set_Number
     (Room   : access Maze_Room;
      Number : in     Positive);

   function Get_Number
     (Room : access Maze_Room) return Positive;

   procedure Set_Side
     (Room      : access Maze_Room;
      Direction : in     Direction_Type;
      Side      : in     Maze_Handle);

   function Get_Side
     (Room      : access Maze_Room;
      Direction : in     Direction_Type) return Maze_Handle;


   type Room_Handle is private;

   function New_Room return Room_Handle;

   function "+"
     (Handle : Room_Handle) return Maze_Room_Access;

   function "+"
     (Handle : Room_Handle) return Maze_Handle;

private

   type Maze_Item_Array is
      array (Direction_Type) of Maze_Handle;

   type Maze_Room is
     new Maze_Item with record
        Sides  : Maze_Item_Array;
        Number : Positive;
     end record;

   procedure Free (Room : access Maze_Room);

   type Room_Handle is new Maze_Handle with null record;

end Mazes.Rooms;
package body Mazes.Storage is

   package body Generic_Item is

      Free_List : Maze_Item_Access;


      procedure Do_Free (Item : access Item_Type) is
      begin
         Item.Next := Free_List;
         Free_List := Maze_Item_Access (Item);
      end;

      function New_Item return Item_Handle is
         Handle : Item_Handle;
      begin

         if Free_List = null then
            Handle.Item :=
              Maze_Item_Access (Item_Access'(new Item_Type));
         else
            Handle.Item := Free_List;
            Free_List := Free_List.Next;
         end if;

         Handle.Item.Count := 1;
         Handle.Item.Next := null;

         return Handle;
      end New_Item;

      function Ref (Handle : Item_Handle) return Item_Access is
      begin
         return Item_Access (Handle.Item);
      end;

   end Generic_Item;

end Mazes.Storage;

private package Mazes.Storage is

   generic

      type Item_Type is new Maze_Item with private;

      type Item_Access is access all Item_Type'Class;

      type Item_Handle is new Maze_Handle with private;

   package Generic_Item is

      procedure Do_Free (Item : access Item_Type);

      function New_Item return Item_Handle;

      function Ref (Handle : Item_Handle) return Item_Access;

   end Generic_Item;

end Mazes.Storage;

with Mazes.Storage;
with Ada.Text_IO;  use Ada.Text_IO;

package body Mazes.Walls.Enchanted is

   type Enchanted_Wall_Access is
      access all Enchanted_Wall'Class;

   package Wall_Storage is
     new Storage.Generic_Item
     (Enchanted_Wall,
      Enchanted_Wall_Access,
      Maze_Handle);


   procedure Enter (Wall : access Enchanted_Wall) is
   begin
      Put_Line ("entered enchanted wall - by walking through it!");
   end;

   function New_Enchanted_Wall return Maze_Handle renames
     Wall_Storage.New_Item;

   procedure Free (Wall : access Enchanted_Wall) renames
     Wall_Storage.Do_Free;


end Mazes.Walls.Enchanted;
package Mazes.Walls.Enchanted is

   type Enchanted_Wall is
     new Maze_Wall with private;

   procedure Enter
     (Wall : access Enchanted_Wall);

   function New_Enchanted_Wall return Maze_Handle;

private

   type Enchanted_Wall is
     new Maze_Wall with null record;

   procedure Free (Wall : access Enchanted_Wall);

end Mazes.Walls.Enchanted;
with Mazes.Storage;
with Ada.Text_IO;

package body Mazes.Walls is

   package Wall_Storage is
     new Storage.Generic_Item
     (Maze_Wall,
      Maze_Wall_Access,
      Maze_Handle);


   procedure Enter (Wall : access Maze_Wall) is
   begin
      Ada.Text_IO.Put_Line ("entered wall");
   end;

   procedure Free (Wall : access Maze_Wall) renames
     Wall_Storage.Do_Free;

   function New_Wall return Maze_Handle renames
     Wall_Storage.New_Item;

end Mazes.Walls;
package Mazes.Walls is

   type Maze_Wall is new Maze_Item with private;

   type Maze_Wall_Access is access all Maze_Wall'Class;


   procedure Enter (Wall : access Maze_Wall);

   function New_Wall return Maze_Handle;

private

   type Maze_Wall is new Maze_Item with null record;

   procedure Free (Wall : access Maze_Wall);

end Mazes.Walls;
package body Mazes is

   procedure Free (Item : access Maze_Item) is
   begin
      null;
   end Free;

   function "+" (Handle : Maze_Handle) return Maze_Item_Access is
   begin
      return Handle.Item;
   end;


   procedure Adjust
     (Handle : in out Maze_Handle) is
   begin
      if Handle.Item /= null then

         Handle.Item.Count := Handle.Item.Count + 1;

      end if;
   end Adjust;


   procedure Finalize
     (Handle : in out Maze_Handle) is
   begin
      if Handle.Item /= null then

         Handle.Item.Count := Handle.Item.Count - 1;

         if Handle.Item.Count = 0 then
            Free (Handle.Item);
         end if;

      end if;
   end Finalize;


end Mazes;
with Ada.Finalization; use Ada.Finalization;

package Mazes is

   pragma Preelaborate;


   type Maze_Item (<>) is abstract tagged limited private;
   --
   -- Named "MapSite" in the GoF book.

   type Maze_Item_Access is access all Maze_Item'Class;


   procedure Enter (Item : access Maze_Item) is abstract;

   type Direction_Type is (North, South, East, West);


   type Maze_Handle is private;

   function "+" (Handle : Maze_Handle) return Maze_Item_Access;

private

   type Maze_Item is
     abstract tagged limited record
        Next  : Maze_Item_Access;
        Count : Natural;
     end record;

   procedure Free (Item : access Maze_Item);


   type Maze_Handle is
     new Controlled with record
        Item : Maze_Item_Access;
     end record;

   procedure Adjust
     (Handle : in out Maze_Handle);

   procedure Finalize
     (Handle : in out Maze_Handle);

end Mazes;

Contributed by: Matthew Heaney
Contributed on: March 8, 1999
License: Public Domain
Back