[Back to OOP SWAG index] [Back to Main SWAG index] [Original]

    {
    From: ichbiah@jdi.tiac.net (Jean D. Ichbiah)

    In a recent article, David Tannen asked me how to achieve information
    in Borland Pascal.  Below is a technique that allows a degree of hiding
    that is close to  that of Ada packages.

    ACHIEVING INFORMATION HIDING WITH BORLAND PASCAL

    How to achieve Information Hiding with Borland Pascal? This is
    an outline of the technique that I have developed and used quite
    systematically in my own programs.

    To motivate the technique, here is the visible part of
    a unit dealing with dictionaries:

    ----------------------------------------------------------------
    }
    unit Dico_Handling;

    interface
    uses Objects;

    type Dictionary  = object
      public
        All_Words   : PCollection;
        All_Phrases : PCollection;

        function First_Word_Index   (C  : Char): Integer; virtual;
        function First_Phrase_Index (C  : Char): Integer; virtual;
      end;

    type Dictionary_name   = ^Dictionary;

    function   New_Dictionary   (File_Name : Pchar): Dictionary_name;
    procedure  Close_Dictionary (G : Dictionary_name);

    implementation
    {
    ------------------------------------------------------------------

    As you see it declares the type Dictionary, the function
    New_Dictionary and the procedure Close_Dictionary. The section
    between "public" and "end" is all you are allowed to know about
    the type Dictionary.

    Then, let us look at the implementation. I am giving a skeleton,
    with enough to explain the reasons of the information hiding
    technique:

    ------------------------------------------------------------------
    }
    implementation
    uses ...;    {  -- several  other units      }

    { Many local decarations. }

    const Sorting = true;

    type Sorted_list = object(TSortedCollection)
      public
        function Compare(Key1, Key2: Pointer): Integer;  virtual;
      end;

    type   Sorted_list_name = ^Sorted_list;

    type   Starter = array [Byte] of Integer;

    type  Full_Dictionary = object(Dictionary)                      {1}
      private
        constructor  Init (Filename : PChar);
        destructor   Done; virtual;
        ...
      private
        ...
        procedure  Read_Dictionary  (Filename: Pchar);
        procedure  Open_Dictionary  (Filename: Pchar);
      private
        ...
        Start_Phrases : Starter;
        Start_Words   : Starter;
      private
        procedure Print_Pairs(C: PCollection);
        procedure Test_Dictionary;
      end;

    type   Full_Dictionary_name   = ^Full_Dictionary;

    { The three operations of Dico_Handling:  }

    function New_Dictionary(File_Name : Pchar): Dictionary_name;    {4}
      var The_New_Dictionary  : Full_Dictionary_name;
    begin
      ...
      The_New_Dictionary := new (Full_Dictionary_name, Init(File_Name));
      ...
      New_Dictionary := The_New_Dictionary;
    end;

    procedure  Close_Dictionary...; begin...end;

    { Methods of Dictionary :  }

    function Dictionary.First_Phrase_Index(C: Char): Integer;        {2}
    begin
      Abstract;
    end;

    function Dictionary.First_Word_Index(C: Char): Integer;          {3}
    begin
      Abstract;
    end;

    {  Methods of Full_Dictionary    }

    constructor  Full_Dictionary.Init (Filename : PChar);            {5}
    begin
      ...
      if Sorting then
        begin
          All_Words   := new(Sorted_list_name, Init(1000, 500));
          All_Phrases := new(Sorted_list_name, Init(1000, 500));
        end
      else
        begin
          All_Words   := new(PCollection, Init(1000, 500));
          All_Phrases := new(PCollection, Init(1000, 500));
        end;

      Read_Dictionary(File_Name);

      Initialize_Starters;
    end;

    procedure Full_Dictionary.Read_Dictionary...; begin...end;
    procedure Full_Dictionary.Open_Dictionary...; begin...end;

    destructor  Full_Dictionary.Done;
    begin
      if All_Words   <> nil then Dispose(All_Words,   Done);
      if All_Phrases <> nil then Dispose(All_Phrases, Done);
    end;

    procedure Full_Dictionary.Initialize_Starters; begin...end;

    { Methods of Sorted_list:  }

    function Sorted_list.Compare...; begin...end;

    end.  {  --  unit Dico_Handling  --  }
    (*
    -------------------------------------------------------------------

    Now, let me explain what I have done:
    =====================================

    At {1} we have the declaration of the type Full_Dictionary,
    derived from Dictionary. As you can see, it is a fully concrete
    type (not an abstract one), with several methods and fields.

    Full_Dictionary, is actually the type that matters for the
    implementation and its methods will represent the bulk
    of the text. On the other hand, at {2} and {3} you can see
    that all methods of Dictionary - the externally visible type -
    are abstract.

    The final supporting leg of the technique happens at {4}:
    in the function New_Dictionary. You are asking for a Dictionary
    and you get a Full_Dictionary! Then the "virtuals" play their role
    so that if you call First_Word_Index or First_Phrase_Index,
    you obtain the effect of these functions as defined for
    the full type: Full_Dictionary.

    The key reason why this works is that the only outside way
    to obtain a Full_Dictionary is by a call to the function
    New_Dictionary, which only allocates Full_Dictionary.  So any
    user of the Unit will be dealing only with objects of this type.

    (Strictly speaking, one could write new(Dictionary_name) but
    nothing could be achieved with such objects since the methods
    are all abstract.)

    Why hide the implementation?
    ============================

    To answer, see what happens if I had made the full type visible.
    This means showing several operations which I have kept hidden:

        constructor  Init (Filename : PChar);
        destructor   Done;    virtual;
        procedure    Read_Dictionary  (Filename: Pchar);
        procedure    Open_Dictionary  (Filename: Pchar);
        procedure    Print_Pairs(C: PCollection);
        procedure    Test_Dictionary;

    Well, if they are visible, it means that other units can use them
    and call them, so that I have to worry about the effect of these
    outside calls. Moreover, if I decide to modify the signature of one of
    them, or delete it, I have to worry about the impacty on outside units.

    If this were not bad enough, consider the field declarations:

        Start_Phrases : Starter;
        Start_Words   : Starter;

    If you had Full_Dictionary in the interface part, they would not be
    allowed UNLESS you also moved the declaration of the type Starter to
    the interface.

    This is a SPAGHETTI effect: you show a field and now you have to show
    its type. You show a type (for a more general example) and you may need
    to show also the constants it is using, and ... whatever type its fields
    need in turn ... Little by little - one spaghetti pulling another one -
    you end up having over inflated interface parts.

    An Additional Benefit
    =====================

    of the technique is illustrated at {5} in the Init constructor.
    What is shown is that you can alternate the implementation
    of the fields All_Words and All_Phrases without this influencing
    outside units. You recompile the unit and relink to test the
    alternate implementations.

    Conclusions
    ============

    I occasionally find myself in the situation of getting a unit
    developed without these concerns for information hiding.
    My first task is then to try to understand the program and this means,
    in particular, to understand the relationship that the unit may
    have with other units.

    Whenever in this situation, I perform my favorite transformation
    which consists in trying to hide as much as I can.  When the
    transformation succeeds, then I know that I have full lattitude
    of modification for the implementation, without fear of impacting
    other outside units.

    The key idea is that most programs are far too exhibitionist
    and show too much in the interface part. So I start hiding as much as
    I can about the types, the reason being to overcome the spaghetti
    effect that I described before.

    Summary of the technique
    ========================

    To hide the types, I start declaring skeletal types such as:

      type Input_Editor = object (TDialog)
          public
          end;

    So the interface of a typical unit will look as follows:

      type Input_Editor = object (TDialog)
          public
          end;

      type Input_Editor_name = ^Input_Editor;

      function New_Input_Editor...: Input_Editor_name;

    (The public-end section is not strictly needed but helps
    emphasize that we know nothing about the type.)  Then in the
    implementation part you will find:

      type Full_Input_Editor_name = object (Input_Editor)
        private
         ...
        end;

      type Input_Editor_name = ^Input_Editor;

    followed by the function

      function New_Input_Editor...: Input_Editor_name;
      begin
        New_Input_Editor := new(Full_Input_Editor_name, init(...));
        ...
      end;

    This is it.  The net result is that the Interface part now exports
    very little.  This means that as a reader, I know now that all
    the rest is purely internal and I need not worry about other
    units using these other constants and types.  As a maintainer,
    it means that I can modify them if needed with well circumscribed
    effects.
    *)

[Back to OOP SWAG index] [Back to Main SWAG index] [Original]