Strategy Pattern


The Strategy pattern provides a means of parameterizing a component to
accept an algorithm.  This keeps the component simpler, and lets clients
pick and choose an algorithm specifically tailored to their needs.

The example in the book parameterizes a composition class (in a text
editor, say) to accept an algorithm for determining line breaks.
Compositors are implemented as stateless classes that have a single
operation, Compose, that returns an array of line break locations.

This implementation, I think, reflects a Smalltalk view of the world.
In Smalltalk, you don't have "free" subprograms, so it's necessary to
wrap the procedure in a class, and pass an instance of that type as the
value of the parameter.  The client would then call Compose as an
operation of the Compositor object.

But an Ada programmer wouldn't do it that way.  You would just declare a
free-standing procedure, and pass that as a generic actual parameter.
This is a far simpler approach that avoids all the heaviness of
type-based solution.

This is the technique I used in the sample code.  The composition type
is provided by a generic package that imports the strategy as a generic
formal subprogram.  Like this:

  generic

     with function Compose
       (Size           : Coordinate_Array;
        Stretchability : Coordinate_Array;
        Shrinkability  : Coordinate_Array;
        Line_Width     : Positive) return Coordinate_Array is <>;

  package Compositions is

     type Composition_Type is limited private;
     ...
  end Compositions;


A client decides which kind of line break algorithm he wants to use and
then instantiates the package on that function.

The simple algorithm determines line breaks on at a time:

  with Compositors.Simple;
  package Compositions is
    new Compositions_G (Compositors.Simple);


The TeX algorithm determines the line breaks for an entire paragraph:

  with Compositors.TeX;
  package Compositions is
    new Compositions_G (Compositors.TeX);


The interval algorithm calculates line breaks at regular intervals.
This algorithm is itself a generic, parameterized by the length of the
interval:

  with Compositors.Generic_Interval;
  function Compositors.Interval_100 is
    new Compositors.Generic_Interval (100);


  with Compositors.Interval_100;
  package Compositions is
    new Compositions_G (Compositors.Interval_100);


Comparing this implementation to the one in the book highlights the fact
that Ada provides facilities for implementing static abstractions that
are different from the facilities for implementing dynamic abstractions.

A Smalltalk programmer doesn't have a choice in the matter, and must use
dynamic abstractions even when he knows at compile-time what he wants.
Ada is a language for systems programming, and in that domain it's
important that you don't pay for things you don't need.

As a matter of fact, we don't even need generics for this example,
because we can just use library-level renaming, like we did for the
abstract factory.

The specification of the "abstract strategy" for breaking lines looks
like it did before:

   function <Virtual Compose>
     (Size           : Coordinate_Array;
      Stretchability : Coordinate_Array;
      Shrinkability  : Coordinate_Array;
      Line_Width     : Positive) return Coordinate_Array;


At compile-time, the client "reifies" the abstract strategy by renaming
one of the concrete implementations.

o Compose implemented using a line-at-a-time algorithm:

  with Compositors.Simple;
  function Compositors.Compose
    (Size           : Coordinate_Array;
     Stretchability : Coordinate_Array;
     Shrinkability  : Coordinate_Array;
     Line_Width     : Positive)
     return Coordinate_Array renames Simple;  <--


o Compose implemented as a paragraph-at-a-time algorithm:

  with Compositors.TeX;
  function Compositors.Compose
    (Size           : Coordinate_Array;
     Stretchability : Coordinate_Array;
     Shrinkability  : Coordinate_Array;
     Line_Width     : Positive)
     return Coordinate_Array renames TeX;  <--


o Compose implemented using regular intervals of 100:

  with Compositors.Interval_100;
  function Compositors.Compose
    (Size           : Coordinate_Array;
     Stretchability : Coordinate_Array;
     Shrinkability  : Coordinate_Array;
     Line_Width     : Positive)
     return Coordinate_Array renames Interval_100;  <--



The composition package (now non-generic) withs the compose strategy as
if it were a normal library-level subprogram:

  with Compositors.Compose;
  package body Compositions is ...;

and thus he statically binds to whatever algorithm was renamed Compose.
This gives you all the benefits of the strategy pattern, with no muss,
and no fuss!

But wait!  There's more!  Act now an you'll get this handsome set of
Ginsu knives, absolutely free!!!

Because I'm feeling generous today, I decided to throw in another
example of the strategy pattern.

When implementing a dynamic abstraction like a linked list or unbounded
stack, you typically use a storage manager to maintain a pool of unused
storage nodes.  Something like this:

  generic
    ...
  package Storage is

     function New_Node return Storage_Node_Access;

     procedure Free (Node : in out Storage_Node_Access);

  end Storage;


What we want to do is provide a "strategy" for finalizing a node when it
gets Free'd, so a client can grant a node last wishes just prior to its
being returned to storage.

We implement this by parameterizing the storage manager with a generic
formal finalization procedure:

  generic
     ...

     with procedure Finalize
       (Node : in out Storage_Node) is Do_Nothing;

  package Storage is ...;


The Finalize procedure gets called as a side-effect of Free'ing a node:

    procedure Free (Node : in out Storage_Node_Access) is
    begin

       if Node = null then
          return;
       end if;

       Finalize (Node.all);     <--
       ...

    end Free;


Note that we supply a default value for Finalize, Do_Nothing, so that
clients who don't need to do anything special during finalization aren't
obligated to write their own dummy procedure that does nothing.

Both client and supplier have to understand the representation of node
type, so we declare that type off in its own package, and import it as a
generic formal package:

  with Storage_Nodes;
  generic

     with package Nodes is new Storage_Nodes (<>);
     use Nodes;

     with procedure Finalize
       (Node : in out Storage_Node) is Do_Nothing;

  package Storage is


The default value of the finalization strategy (procedure Do_Nothing)
comes from the nodes package, which is where we declare the node type:

  generic
     type Item_Type is limited private;
  package Storage_Nodes is

     type Storage_Node;
     type Storage_Node_Access is access all Storage_Node;

     type Storage_Node is
        limited record
           Item : aliased Item_Type;
           Next : Storage_Node_Access;
        end record;

     procedure Do_Nothing (Node : in out Storage_Node);    <--

  end Storage_Nodes;


Let's see how this gets used by creating two simple abstractions, a
linked list and an unbounded stack.

First we implement the list as a pointer to a storage node:

  generic
    type Item_Type is private;
  package Lists is

    type List_Type is private;
    ...

  private

    package Nodes is new Storage_Nodes (Item_Type);
    use Nodes;

    type List_Type is
       record
          Head : Storage_Node_Access;
       end record;

   end Lists;


When we Clear the list, we want all the nodes in the chain to get freed.
We can effect this by implementing Finalize so that it frees the next
node, which finalizes that node, which frees next one, and so on
recursively:

  package body Lists is

    procedure Finalize (Node : in out Storage_Node);

    package List_Storage is new Storage (Nodes, Finalize);  <--
    use List_Storage;

    procedure Finalize (Node : in out Storage_Node) is
    begin
       Free (Node.Next);
    end;

Clear is implemented by just Free'ing the list head:

    procedure Clear (List : in out List_Type) is
    begin
       Free (List.Head);
    end;

which causes all the nodes in the list to get freed.

The list abstraction we just saw has a specific strategy for node
finalization, and therefore supplies a non-default implementation to the
instantiation of the storage manager.  Now let's implement an unbounded
stack that doesn't need to do anything, and so can take the default.

Like we did for the list, we implement our stack as a list of nodes:

  generic
     type Item_Type is private;
  package Unbounded_Stacks is

     type Stack_Type is limited private;
     ...
  private

     package Nodes is new Storage_Nodes (Item_Type);
     use Nodes;

     type Stack_Type is
        limited record
           Top : Storage_Node_Access;
        end record;

  end Unbounded_Stacks;


This particular stack has doesn't need to clear the stack, so we just
instantiate the storage manager as is:

  package body Unbounded_Stacks is

     package Stack_Storage is new Storage (Nodes);  <--
     use Stack_Storage;


Compare this to the list example, noting how the list overrides the
default value of Finalize, and the stack takes the default.


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

--STX
package body Compositions_G is

   procedure Repair (Composition : in out Composition_Type) is

      subtype Coordinates_Range is
        Positive range 1 .. Composition.Component_Count;

      subtype Coordinates is
        Coordinate_Array (Coordinates_Range);

      Size           : Coordinates;
      Stretchability : Coordinates;
      Shrinkability  : Coordinates;

      Breaks : constant Coordinate_Array :=
        Compose (Size           => Size,
                 Stretchability => Stretchability,
                 Shrinkability  => Shrinkability,
                 Line_Width     => Composition.Line_Width);
   begin

      null;

   end Repair;

end Compositions_G;

with Coordinate_Types; use Coordinate_Types;

generic

   with function Compose
     (Size           : Coordinate_Array;
      Stretchability : Coordinate_Array;
      Shrinkability  : Coordinate_Array;
      Line_Width     : Positive) return Coordinate_Array is <>;

package Compositions_G is

   type Composition_Type is limited private;

   procedure Repair (Composition : in out Composition_Type);

private

   type Composition_Type is
      record
         Line_Width      : Positive := 80;
         Line_Count      : Natural;
         Component_Count : Natural;
      end record;

end Compositions_G;
function Compositors.Generic_Interval
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array is

   Breaks : Coordinate_Array (Size'Range);
begin
   return Breaks;
end;


with Coordinate_Types; use Coordinate_Types;

generic
   Interval : in Positive;
function Compositors.Generic_Interval
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array;
function Compositors.Simple
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array is

   Breaks : Coordinate_Array (Size'Range);
begin
   return Breaks;
end;


with Coordinate_Types; use Coordinate_Types;

function Compositors.Simple
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array;
function Compositors.TeX
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array is

   Breaks : Coordinate_Array (Size'Range);
begin
   return Breaks;
end;


with Coordinate_Types; use Coordinate_Types;

function Compositors.TeX
  (Size           : Coordinate_Array;
   Stretchability : Coordinate_Array;
   Shrinkability  : Coordinate_Array;
   Line_Width     : Positive) return Coordinate_Array;
package Compositors is

   pragma Pure;

end Compositors;
package Coordinate_Types is

   pragma Pure;


   type Coordinate_Type is
      record
         null;  -- <whatever goes here>
      end record;

   type Coordinate_Array is
      array (Positive range <>) of Coordinate_Type;

end Coordinate_Types;



with Lists;
package Integer_Lists is new Lists (Integer);
with Unbounded_Stacks;
package Integer_Stacks is new Unbounded_Stacks (Integer);

with Storage;

package body Lists is

   procedure Finalize (Node : in out Storage_Node);

   package List_Storage is new Storage (Nodes, Finalize);
   use List_Storage;

   procedure Finalize (Node : in out Storage_Node) is
   begin
      Free (Node.Next);
   end;


   function Cons (Item : Item_Type;
                  List : List_Type) return List_Type is

      Node : constant Storage_Node_Access := New_Node;
   begin
      Node.Item := Item;
      Node.Next := List.Head;

      return List_Type'(Head => Node);
   end Cons;


   function Get_Head (List : List_Type) return Item_Type is
   begin
      return List.Head.Item;
   end;

   procedure Set_Head (List : in List_Type;
                       Item : in Item_Type) is
   begin
      List.Head.Item := Item;
   end;


   function Get_Tail (List : List_Type) return List_Type is
   begin
      return List_Type'(Head => List.Head.Next);
   end;


   procedure Set_Tail (List : in List_Type;
                       Tail : in List_Type) is
   begin
      List.Head.Next := Tail.Head;
   end;


   procedure Clear (List : in out List_Type) is
   begin
      Free (List.Head);
   end;

end Lists;
with Storage_Nodes;

generic
  type Item_Type is private;
package Lists is

   type List_Type is private;

   Null_List : constant List_Type;


   function Cons (Item : Item_Type;
                  List : List_Type) return List_Type;

   function Get_Head (List : List_Type) return Item_Type;

   procedure Set_Head (List : in List_Type;
                       Item : in Item_Type);

   function Get_Tail (List : List_Type) return List_Type;

   procedure Set_Tail (List : in List_Type;
                       Tail : in List_Type);

   procedure Clear (List : in out List_Type);

private

   package Nodes is new Storage_Nodes (Item_Type);
   use Nodes;

   type List_Type is
      record
         Head : Storage_Node_Access;
      end record;

   Null_List : constant List_Type := (Head => null);

end Lists;
package body Storage is

   Free_List : Storage_Node_Access;


   function New_Node return Storage_Node_Access is

      Node : Storage_Node_Access;

   begin

      if Free_List = null then
         Node := new Storage_Node;
      else
         Node := Free_List;
         Free_List := Free_List.Next;
         Node.Next := null;
      end if;

      return Node;

   end New_Node;


   procedure Free (Node : in out Storage_Node_Access) is
   begin

      if Node = null then
         return;
      end if;

      Finalize (Node.all);

      Node.Next := Free_List;
      Free_List := Node;
      Node := null;

   end Free;

end Storage;







with Storage_Nodes;

generic

   with package Nodes is new Storage_Nodes (<>);

   use Nodes;

   with procedure Finalize
     (Node : in out Storage_Node) is Do_Nothing;

package Storage is

   function New_Node return Storage_Node_Access;

   procedure Free (Node : in out Storage_Node_Access);

end Storage;







package body Storage_Nodes is

   procedure Do_Nothing (Node : in out Storage_Node) is
   begin
      null;
   end;

end Storage_Nodes;







generic
   type Item_Type is limited private;
package Storage_Nodes is

   type Storage_Node;
   type Storage_Node_Access is access all Storage_Node;

   type Storage_Node is
      limited record
         Item : aliased Item_Type;
         Next : Storage_Node_Access;
      end record;

   procedure Do_Nothing (Node : in out Storage_Node);

end Storage_Nodes;







with Integer_Lists;  use Integer_Lists;
with Ada.Text_IO;    use Ada.Text_IO;

procedure Test_List is

   List : List_Type;

begin

   List := Cons (1, Null_List);
   List := Cons (2, List);
   List := Cons (3, List);

   declare
      Index : List_Type := List;
   begin
      while Index /= Null_List loop
         Put (Integer'Image (Get_Head (Index)));
         Put (" ");

         Index := Get_Tail (Index);
      end loop;

      New_Line;
   end;

   Clear (List);

end Test_List;



with Storage;

package body Unbounded_Stacks is

   package Stack_Storage is new Storage (Nodes);
   use Stack_Storage;


   procedure Push
     (Item : in     Item_Type;
      On   : in out Stack_Type) is

      Node : constant Storage_Node_Access := New_Node;
   begin
      Node.Item := Item;
      Node.Next := On.Top;

      On.Top := Node;
   end Push;


   procedure Pop
     (Stack : in out Stack_Type) is

      Node : Storage_Node_Access := Stack.Top;
   begin
      Stack.Top := Stack.Top.Next;
      Free (Node);
   end Pop;


   function Get_Top
     (Stack : Stack_Type) return Item_Type is
   begin
      return Stack.Top.Item;
   end;

end Unbounded_Stacks;

with Storage_Nodes;

generic
   type Item_Type is private;
package Unbounded_Stacks is

   type Stack_Type is limited private;

   procedure Push
     (Item : in     Item_Type;
      On   : in out Stack_Type);

   procedure Pop
     (Stack : in out Stack_Type);

   function Get_Top
     (Stack : Stack_Type) return Item_Type;

private

   package Nodes is new Storage_Nodes (Item_Type);
   use Nodes;

   type Stack_Type is
      limited record
         Top : Storage_Node_Access;
      end record;

end Unbounded_Stacks;

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

Back