[Back to OOP SWAG index] [Back to Main SWAG index] [Original]
My understanding of OOP revolves around three principles:
ENCAPSULATION: All data-Types, Procedures, Functions are placed
within a new Type of wrapper called an Object.
This new wrapper is very simillar to a standard
Record structure, except that it also contains
the routines that will act on the data-Types
within the Object.
The Object-oriented style of Programming requires
that you should ONLY use the routines within the
Object to modify/retrieve each Object's data-Types.
(ie: Don't access the Variables directly.)
Structured Style OOP Style
================ =========
MyRecord = Record MyObject = Object
1st Variable; 1st Variable;
2nd Variable; 2nd Variable;
3rd Variable 3rd Variable;
end; Procedure One;
Procedure Two;
Function One;
Function Two;
end;
inHERITANCE: This gives you the ability to make a new Object by
cloning an old Object. The new Object will contain
all the abilities of the old Object.
(ie: Variables, Procedures/Functions).
You can add additional abilities to this new Object,
or replace old ones.
+--------------+
| New Object |
| +--------+ |
| | Old | |
| | Object | |
| +--------+ |
+--------------+
With Inheritance, you don't have to go back and
re-Write old routines to modify them into new
ones. Instead, simply clone the old Object and
add or replace Variables/Procedures/Functions.
This makes the whole process of rewriting/modifying
a Program MUCH faster/easier. Also there is less
chance of creating new bugs from your old bug-free
source-code.
POLYMorPHISM: The name Sounds intimidating, but the concept is
simple.
Polymorphism allows one Procedure/Function to
act differently between one Object and all its
descendants. (Clones)
These Type of "polymorphic" Procedures/Functions
know which Object they are working on, and act
accordingly. For example:
Say you've created an Object (Object-1) that
contains a Procedure called DrawWindow, to draw
the main screen of a Program.
DrawWindow relies on another Procedure SetBorder
within Object-1, to set the borders used in the
main screen.
Now you clone Object-2 from Object-1.
You want to use Object-2 to handle pop-up Windows,
but you want the pop-ups to have a different border
style.
if you call the DrawWindow Procedure that Object-2
inherited from Object-1, you'll end up With a Window
With the wrong border-style.
to get around this you could change the SetBorder
Procedure to a "Virtual" Procedure, and add a
second identically named "Virtual" Procedure
(SetBorder) within Object-2.
A "Virtual" Procedure relies on a "Virtual Table"
(Which is basicly a Chart to indicate which
"Virtual" routine belongs to which Object)
to, indicate which version of the identically
named Procedures should be used within different
Objects.
So within Object-1, the DrawWindow routine will
use the SetBorder Procedure within Object-1.
Within Object-2, the inherited DrawWindow routine
will use the other SetBorder Procedure that belongs
to Object-2.
This works because the "Virtual Table" tells the
DrawWindow routine which SetBorder Procedure to
use For each different Object.
So a call to the SetBorder Procedure now acts
differently, depending on which Object called it.
This is "polymorphism" in action.
OOP LANGUAGE LinGO: The following are some of the proper names For
OOP syntax.
Structured Programming OOP Programming
====================== ===============
Variables Instances
Procedures/Functions Methods
Types Classes
Records Objects
{
> i have a parent Object defined With Procedure a and b.
> i have a child Object With Procedure a, b and c.
> when i declare say john being a child, i can use a, b, or c With no
> problem. when i declare john as being a parent, i can use a or b.
> if i declare john as being a parent and initialise it with
> new (childPTR,init) it seems i have access to the parent fields
After reading twice, I understand you mean Object classes dealing With humans,
not trees (happen to have parents & childs too).
> parent a,b,c,d,e,f
(bad)
> parent a,b
(good)
> child a,b,c
> child2 a,b,d
> child3 a,b,e,f
(redefine a, b For childs as Far as they differ from parent a,b)
Next example could be offensive For christians, atheists and media-people.
}
Type
TParent = Object { opt. (tObject) For Stream storage }
Name : String;
Constructor Init(AName: String);
Procedure Pray; { your A,
they all do it the same way }
Procedure Decease; Virtual; { your B, Virtual, some instances
behave different (Heaven/Hell) }
Destructor Done; Virtual;
end;
TChild1 = Object(TParent)
Disciples : Byte;
Constructor Init(AName: String; DiscipleCount: Byte);
{ do not override Decease } { calling it will result in a
call to TParent.Decease }
Procedure Resurrection; { your C }
end;
TChild2 = Object(TParent)
BulletstoGo : LongInt;
Constructor Init(DisciplesCount: Byte; Ammo: LongInt);
Procedure Decease; Virtual; { override }
Procedure Phone(Who: Caller); { your D }
end;
Constructor TParent.Init(AName: String);
begin
Name := AName;
end;
Destructor TParent.Done;
begin
{(...)}
end;
Procedure TParent.Pray;
begin
ContactGod;
end;
Procedure TParent.Decease;
begin
GotoHeaven;
end;
Constructor TChild1.Init(AName: String; DiscipleCount: Byte);
begin
inherited Init(AName);
Disciples := DiscipleCount;
end;
Procedure TChild1.Resurrection;
begin
RiseFromTheDead;
end;
Constructor TChild2.Init(AName: String;
DiscipleCount: Byte; Ammo: LongInt);
begin
inherited Init(DiscipleCount);
BulletstoGo := Ammo;
end;
Procedure TChild2.Decease;
begin
EternalBurn;
end;
Procedure TChild2.Phone(Who: Caller);
begin
Case Who of
AFT : Ventriloquize;
Media : Say('Burp');
end;
end;
{
In the next fragment all three Types of instances are put into a collection.
}
Var
Christians : PCollection;
begin
Christians := New(PCollection, Init(2,1));
With Christians^ do begin
Insert(PParent, Init('Mary'));
Insert(PParent, Init('John'));
Insert(PChild1, Init('Jesus', 12));
Insert(PChild2, Init('Koresh', 80, 1000000));
end;
{
Now you can have all instances pray ...
}
Procedure DoPray(Item: Pointer); Far;
begin
{ unTyped Pointers cannot have method tables. The PParent
Typecast Forces a lookup of Pray in the method table.
All instances With a TParent ancestor will point to
the same (non-Virtual) method }
PParent(Item)^.Pray;
end;
{ being sure all Items in Christians are derived from TParent }
Christians^.ForEach(@DoPray);
{
and because all mortals will die...
}
Procedure endVisittoEarth(Item: Pointer); Far;
begin
{ Decease is a Virtual method. The offset of a location in
the VMT With the address of a Virtual method is determined by
the Compiler. At run-time, For each Type of instance 1 VMT
will be created, it's method-fields filled With the
appropriate addresses to call.
Each instance of an Object derived from TParent will have the
address of it's VMT at the same location. Calling a Virtual
method results in
1) retrieving that VMT address at a known offset in
the instance's data structure
2) calling a Virtual method at a known offset in the
VMT found in 1)
ThereFor mr. Koresh will go to hell: PChild2's VMT contains
at the offset For Decease the address of the overridden
method. Mr. Jesus, a PChild1 instance, simply inherits the
address of PParent's Decease method at that offset in the
VMT. }
PParent(Item)^.Decease;
end;
Christians^.ForEach(@endVisittoEarth);
-> ...I've no problem posting my code, but I'm still not Really happy
-> With it's present Implementation. I also don't think that dynamic
-> Array Objects are very good examples of OOP. (For example, what
-> do extend the dynamic-Array Object into, via inheiritance???)
->
-> ...Something more like a generic "Menu" or "Line-Editor" Object
-> might be a better example.
Well I don't know exactly what you are trying to do With your dynamic
Array but it can be OOP'ed. Linked lists are a prime example (I hope
this is close) By using OOP to Write link lists you can come up with
Objects such as:
Type
ListPtr = ^List;
NodePtr = ^ListNode;
List (Object)
TNode : Pointer; {Pointer to the top Record}
BNode : Pointer; {Pointer ro the bottom Record}
CurNode : Pointer; {Current Pointer}
Constructor Init; {Initializes List Object}
Destructor Done; Virtual; {Destroys the list and all its nodes}
Function top (Var Node : ListNode) : NodePtr;
Function Bottom (Var Node : ListNode) : NodePtr;
Function Next (Var Node : ListNode) : NodePtr;
Function Prev (Var Node : ListNode) : NodePtr;
Function Current(Var Node : ListNode) : NodePtr;
Procedure AttachBeFore (Var Node : ListNode);
Procedure AttachAfter (Var Node : ListNode);
Procedure Detach (Var NodePtr : Pointer);
end;
ListNode = Object;
Prev : NodePtr;
Next : NodePtr;
Constructor Init;
Destructor Done; Virtual;
end;
The list Object is just that. It has the basic operations you would do
with a list. You can have more than one list but only one set of
methods will be linked in. The List node Dosn't have much other than
the Pointers to link them into a list and an Init, done methods. Sounds
like a ton of work just to implement a list but there is so much you can
do easely With OOP that you would have a hard time doing conventionally.
One example, because the ListNode's Done Destructor is Virtual the Done
of the list can accually tranvirs the list and destroy all Objects in
the list. One list can accually contain Objects that are not the
same!!! Yep it sure can. As long as an Object is dirived from ListNode
the list can handel it. Try to do that using conventional methods!!
I'm assuming that your dynamic Array will do something similar which is
why I suggested it. A Menu and Line editor Objects are High level
Objects that should be based on smaller Objects. I'd assume that a line
editor would be a Complex list of Strings so the list and ListNode
Objects would need to be built. See what I mean???
then you get into Abstract Objects. These are Objects that define
common methods For its decendants but do not accually have any code to
suport them. This way you have set up a standard set of routines that
all decendants would have and Programs could be written using them. THe
results of which would be a Program that could handel any Object based
on the abstract.
-> RM>I have mixed feeling on this. I see OOP and Object as tools For a
-> RM>Program to manipulate.
->
-> RM> IE: File Objects, Screen Objects, ect then bind them together
-> RM> in a Program using conventional style coding.
->
-> ...to my understanding of the OOP style of Programming, this would
-> be a "NO-NO".
OK well With the exception of TApplication Object in Turbo Vision a
Program is a speciaized code that more than likely can't be of any use
For decendants. That was my reasioning at least. and the Tapp Object
isn't a Program eather. YOu have to over ride a ton of methods to get
it to do anything.
Unit OpFile; {******* Capture this For future referance *******}
Interface
Type
DateTimeRec = Record
{Define the fields you want For date and time routines}
end;
AbstractFile = Object
Function Open : Boolean; Virtual;
{Opens the File in the requested mode base on internal Variables }
{Returns True if sucessfull }
Procedure Close; Virtual;
{Flush all buffers and close the File }
Function Exists : Boolean; Virtual;
{Returns True is the File exists }
Function Create : Boolean; Virtual;
{Will create the File or overWrite it if it already exists }
Procedure Delete; Virtual;
{Will delete the File. }
Function Rename : Boolean; Virtual;
{Will rename the File returns True if successfull }
Function Size : LongInt; Virtual;
{Returns the size of the File. }
Procedure Flush; Virtual;
{Will flush the buffers without closing the File. }
Function Lock : Boolean; Virtual;
{Will attempt to lock the File in a network enviroment, returns }
{True if sucessfull }
Procedure Unlock; Virtual;
{Will unlock the File in a network enviroment }
Function Copy (PathName : String) : Boolean; Virtual;
{Will copy its self to another File, returns True is successfull.}
Function GetDateTime (Var DT : DateTimeRec) : Boolean; Virtual;
{Will get the File date/time stamp. }
Function SetDateTime (Var DT : DateTimeRec) : Boolean; Virtual;
{Will set the File date stamp. }
Function GetAttr : Byte; Virtual;
{Will get the File attributes. }
Function SetAttr (Atr : Byte) : Boolean; Virtual;
{Will set a File's attributes. }
end; {of AbstractFile Object}
Implementation
Procedure Abstract; {Cause a run time error of 211}
begin
Runerror (211);
end;
Function AbstractFile.Open : Boolean;
begin
Abstract;
end;
Procedure AbstractFile.Close;
begin
Abstract;
end;
Function AbstractFile.Exists : Boolean;
begin
Abstract;
end;
Function AbstractFile.Create : Boolean;
begin
Abstract;
end;
Procedure AbstractFile.Delete;
begin
Abstract;
end;
Function AbstractFile.Rename : Boolean;
begin
Abstract;
end;
Ok theres a few things we have to talk about here.
1. This is an ABSTRACT Object. It only defines a common set of
routines that its decendants will have.
2. notice the Procedure Abstract. It will generate a runtime error
211. This is not defined by TP. Every Method of an Object has to do
somthing. if we just did nothing we could launch our Program into
space. By having all methods call Abstract it will error out the
Program and you will know that you have called and abstract method.
3. I'm sure some may question why some are Procedures and some are
Functions ie Open is a Function and close is a Boolean. What I based
them on is if an error check a mandatory it will be a Function Boolean;
This way loops will be clean. Open in a network Open will require a
check because it may be locked. Which brings up point 4.
4. We are not even finished With this Object yet. We still have to
define a standard error reporting / checking methods and also lock loop
control methods. not to mention some kind of common data and methods to
manipulate that data. Moving to point 5.
5. Where does it end??? Well we hvae added quite a few Virtual methods
While thsi is not bad it does have a negative side. All Virtual methods
will be linked in to the final EXE weather it is used or not. There are
valid reasions For this but you don't want to make everything Virtual if
it Dosn't have to be. My thinking is this. if it should be a standard
routine For all decendants then it should be Virtual. if required
methods call a method then why not make it Virtual (this will become
more apparent in network methods and expanding this Object)
Now personally I get a feeling that the DateTime and Attr methods
shouldnn't be there or at least not Virtual as the vast majority of
Programs will not need them and its pushing the limits of Operating
system spisific methods. SO it will probly be a Dos only Object. (Yes
there are others that have this but I think its over kill) The same
goes For the copy and rename methods so I would lean to removing them
from this Object and define them in decendants.
So what do you think we need to have For error checking / reporting
methods??? Do you think we could use more / different methods???
{
DW> I am trying to teach myself about Object orientated Programming and
DW> about 'inheritence'. This is my code using Records.
The idea of Object oriented Programing is what is refered to as
encapsulation. Your data and the Functions that manipulate it are
grouped together. As an example, in a traditional Program, a linked
list would look something like:
}
Type
Linked_List =
Record
Data : Integer; {Some data}
Next : ^Linked_List; {Next data}
Prev : ^Linked_List; {Prev data}
end;
then you would have a whole slew of Functions that took Linked_List as a
parameter. Under OOP, it would look more like
Type
Linked_List =
Object
Data : Integer;
Next : ^Linked_List;
Prev : ^Linked_List;
Constructor Init(); {Initializes Linked_List}
Destructor DeInit(); {Deinitializes Linked_List}
Procedure AddItem(aData : Integer);
Procedure GetItem(Var aData : Integer);
end;
then, to add an item to a particular list, the code would look like:
This_Linked_List.AddItem(10);
This is easier to understand. An easy way to think about this is that
an Object is an entity sitting out there. You tell it what you want to
do, instead of calling a Function you can't identify. Inheritance
allows you to make a linked list that holds a different Type, but Uses
the same Interface funtions. More importantly, using the same method
and Pointers, you could have both Types in the same list, depending on
how you implemented it.
It helps debugging time, because if you wanted to add a Walk_List
Function, you could add it and get it working For the parent Object, and
(since the mechanics of it would be the same For ANY Linked List), you
could Write it once and use it without problems. That is a clear
advantage. Other Uses include:
(For a door Type Program) and Input/Output Object that serves as a base
For a console Object and a modem Object, and thusly allows you to treat
the two as the same device, allowing you to easily use both.
(For a BBS Message base kit) a Generic Message Object that serves as a
base For a set of Objects, each of which implements a different BBS'
data structures. Using this kit, a Program could send a message to any
of the BBSes just by knowing the core Object's mechanics.
(For Windows) a Generic Object represents a Generic Window. By
inheritance, you inherit the Functionality of the original Window. By
creating an Object derived from the generic Window, you can add
additional Functionality, without having to first Write routines to
mirror existing Functionality.
(For Sound) a Generic Object represents a generic Sound device.
Specific child Object translate basic commands (note on, note off, etc)
to device specific commands. Again, the Program doesn't have to know
whether there is a PC speaker or an Adlib or a SoundBlaster--all it has
to know is that it calls note_on to start a note and note_off to end a
note.
There are thousands on thousands of other examples. if you read through
the turbo guides to turbovision or to Object oriented Programming, they
will help you understand. Also, a good book on Object oriented
Programming doesn't hurt ;>.
{
> Now, the questions:
> 1. How do I discretly get the Lat & Long into separate
> Collections? In other Words (psuedocode):
No need For seperate collections, put all the inFormation in a Single
collection.
> Any hints would be appreciated. Thanks!
I'll not give any help With parsing the Text File, there will probably be a ton
of advice there, but here is a little Program that I threw together (and
tested) that will list the inFormation and present the additional data.
Have fun With it.
}
Program Test;
Uses Objects,dialogs,app,drivers,views,menus,msgbox;
Type
(*Define the Data Element Type*)
Data = Record
Location : PString;
Long,Lat : Real;
end;
PData = ^Data;
(*Define a colection of the data elements*)
DataCol = Object(TCollection)
Procedure FreeItem(Item:Pointer); Virtual;
end;
PDC =^DataCol;
(*Define a list to display the collection*)
DataList = Object(TListBox)
Function GetText(item:Integer;maxlen:Integer):String; Virtual;
Destructor done; Virtual;
end;
PDL = ^DataList;
(*Define a dialog to display the list *)
DataDlg = Object(TDialog)
Pc : PDC;
Pl : PDL;
Ps : PScrollBar;
Constructor Init(Var bounds:Trect;Atitle:TTitleStr);
Procedure HandleEvent(Var Event:TEvent); Virtual;
end;
PDD = ^DataDlg;
Const
CmCo = 100;
CmGo = 101;
Procedure DataCol.FreeItem(Item:Pointer);
begin
disposeStr(PString(PData(Item)^.Location));
dispose(PData(Item));
end;
Function DataList.GetText(item:Integer;maxlen:Integer):String;
begin
GetText := PString(PData(List^.At(item))^.Location)^;
end;
Destructor DataList.Done;
begin
Dispose(PDC(List),Done);
TListBox.Done;
end;
Constructor DataDLG.Init(Var bounds:Trect;Atitle:TTitleStr);
Var
r : trect;
pd : pdata;
begin
TDialog.Init(bounds,ATitle);
geTextent(r); r.grow(-1,-1); r.a.x := r.b.x - 1; dec(r.b.y);
new(ps,init(r)); insert(ps);
geTextent(r); r.grow(-1,-1); dec(r.b.x); dec(r.b.y);
new(pl,init(r,1,ps)); insert(pl);
geTextent(r); r.grow(-1,-1); r.a.y := r.b.y - 1;
insert(new(pstatusline,init(r,
newstatusdef(0,$FFFF,
newstatuskey('~[Esc]~ Quit ',kbesc,CmGo,
newstatuskey(' ~[Alt-C]~ Co-ordinates ',kbaltc,CmCo,
newstatuskey('',kbenter,CmCo,nil))),nil))));
new(Pc,init(3,0));
With pc^ do (*parse your File and fill the*)
begin (*collection here *)
new(pd);
pd^.location := newstr('Port Arthur, Texas');
pd^.long := 29.875; pd^.lat := 93.9375;
insert(pd);
new(pd);
pd^.location := newstr('Port-au-Prince, Haiti');
pd^.long := 18.53; pd^.lat := 72.33;
insert(pd);
new(pd);
pd^.location := newstr('Roswell, New Mexico');
pd^.long := 33.44118; pd^.lat := 104.5643;
insert(pd);
end;
Pl^.newlist(pc);
end;
Procedure DataDlg.HandleEvent(Var Event:TEvent);
Var
los,las : String;
begin
TDialog.HandleEvent(Event);
if Event.What = EvCommand then
Case Event.Command of
CmGo : endModal(Event.Command);
CmCo : begin
str(PData(Pl^.List^.At(Pl^.Focused))^.Long:3:3,los);
str(PData(Pl^.List^.At(Pl^.Focused))^.Lat:3:3,las);
MessageBox(
#3+PString(PData(Pl^.List^.At(Pl^.Focused))^.Location)^ +
#13+#3+'Longitude : '+los+#13+#3+'Latitude : '+las,
nil,mfinFormation+mfokbutton);
end;
end;
end;
Type (*the application layer *)
myapp = Object(Tapplication)
Procedure run; Virtual;
end;
Procedure myapp.run;
Var r:trect;
p:PDD;
begin
geTextent(r);
r.grow(-20,-5);
new(p,init(r,'Dialog by ken burrows'));
if p <> nil then
begin
desktop^.execview(p);
dispose(p,done);
end;
end;
Var
a:myapp;
begin
a.init;
a.run;
a.done;
end.
> I am having a problem. I would like to Write an editor. The
> problem is I dont understand a thing about Pointers (which everyone
> seems to use For editors).
I'm certainly no TP expert, but I might be able to help out With the
Pointers. Pointers are just special 4-Byte Variables that contain (
point to) a specific position in memory. You can also make a Pointer
act like the thing to which it is pointing is a particular Type of
Variable (Byte, String, etc). Unlike normal Var Variables, however, these
Variables are what's referred to as Virtual -- they aren't fixed in the
.EXE code like Var Vars, so you can have as many of them as you like,
within memory Constraints. Each is created when needed using the GetMem
statement. This statement makes a request For some more memory to be
used in the heap (all left-over memory when the Program loads usually).
What you need in a editor is to be able to somehow link the Strings
that make up the document into what's called a list (first line, next,
... , last line). The easiest way to visualize this is a bunch of people
in a line holding hands, each hand being a Pointer. The hand is not the
entire person, it just connects to the next one. So, what you do is
use a Record that contains one String For one line of Text, a Pointer to
the previous line of Text in the document, and a Pointer to the next line
in the document. A Record like this should do it:
{+------------------------- Usually used in starting a Type of Pointer}
{|+------------------------ Points to a String in the document }
{|| +----------- This is usedto mean that PStringItem is }
|| | to be a Pointer pointing to a Record }
|| | known as TStringItem }
{vv v
Type PStringItem = ^TStringItem;
TStringItem : Record
LineOText : String [160]; {Double the screen width should do it}
NextLine : PStringItem; {Points to the next line in memory}
PrevLine : PStringItem; {Points to the previous line in memory}
end;
In your editor main Program, use
Var FirstLine, LastLine, StartLine, CurrLine : PStringItem;
to create Varibles giving you `bookmarks' to the first line in the
File, last in the File, the one the cursor is on, and the one that
starts the screen. All of these will change.
to create the first line in the document, use:
GetMem (FirstLine, Sizeof (TStringItem)); {get memory enough For one line}
CurrLine := FirstLine; {of course, only one line in the doc so Far!}
LastLine := FirstLine;
StartLine := FirstLine;
FirstLine^.NextLine := nil; {nil means no particular place-- there's no}
FirstLine^.PrevLine := nil; {line beFore of after FirstLine yet }
Now the Variable FirstLine will contain the address of the newly created
Variable. to address that Variable, use the carrot (^), like this:
FirstLine^.LineOText := 'Hello World!');
to make a new line in the list just get more memory For another line:
GetMem (LastLine^.NextLine, Sizeof (TStringItem));
LastLine := LastLine^.NextLine;
This will get more memory and set the last line in the File's
next line Pointer to the new String, then make the new String the
last line.
Deleting a line is almost as simple. You use the FreeMem Procedure
to release the memory used by a Variable. if it's in the middle of the
list, just set the to-be-deleted's next line's previous line to the
to-be deleted's previous line, and the previous line's next to the one
after the one to be deleted, essentially removing it from the list and
then tieing the peices back together. You can then kill off the memory
used by that line.
{Delete current line}
if CurrLine^.NextLine <> nil then {there's a line after this one}
CurrLine^.NextLine^.PrevLine := CurrLine^.PrevLine;
if CurrLine^.PrevLine <> nil then {there's a line beFore this one}
CurrLine^.PrevLine^.NextLine := CurrLine^.NextLine;
FreeMem (CurrLine, Sizeof (TStringItem));
to insert a line, just do about the opposite.
if you don't understand, I won't blame you, I'm half asleep anyway...
but I hoe it clears some of the fog. if the manual isn't helpful
enough now, try tom Swan's _Mastering Turbo Pascal_, an excellent
book.
[Back to OOP SWAG index] [Back to Main SWAG index] [Original]