[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]