Hi! I hope that the Object Oriented Programming (OOP) tutorial really helps. You may ask: How should we design an application with OOP approach. Well, first I will explain a bit about how can we build a component using OOP approach. The component I chose to show you is the infamous linked list. Well, I don't want to explain linked list itself since I have explained it in lesson 2. I just give a brief explanation on it and emphasize on the OOP methodologies.
Please keep in mind that the linked list we build here will be used in the next chapter. The next chapter will discuss a broader aspect of OOP-styled application.
OK, let's suppose that we'd like to build a linked list that store only a name and a score. A typical link list class that you'd create will look like this:
type PMyData = ^TMyData; TMyData = record name : String; score : byte; next : PMyData; end;
Ah... that's familiar, right. Now, we'd like to decouple the data from the linked list itself. Why? That's because if we make the linked list implementation separate from any data, we can make the linked list generally reusable for many kinds of data. So, the linked list object will look something like this:
type PMyLinkedList = ^TMyLinkedList; TMyLinkedList = object : data : TMyData; next : PMyLinkedList; : end;
That's not finished yet. Now, we have to declare the data as a class as well.
type PMyData = ^TMyData; TMyData = object private name : String; score : byte; public { Accessors } procedure SetName(n: String); function GetName: String; procedure SetScore(n: byte); function GetScore: byte; end;
See, there's no next field now. It's been separated. Also, remember that in OOP methodology, it is [almost] always a good practice to make fields as private. Why? It is to avoid accidental access by unauthorized methods. Well, this may be a small example and doing this may cause inefficiencies. Anyway, I just show you some examples to follow so that in larger projects where there are myriads of class declarations, you will be saved from headache by following this rule of thumb.
Since fields are private, we need to make it accessible for other classes. Therefore, we provide accessors. Remember that accessors are the methods that get or set the field value. As you observe that Get... methods and Set... methods are declared there for each fields. Of course, if you build your own class, you can make the fields read-only by declaring only Get... accessor. Similarly, making the fields write-only is just declaring Set... accessor. The fields internal to the class (i.e. need not to be accessed from outside) have no accessor. Thus, minimizing the interference from outside the class (and of course saving you from silly bugs).
Now, how can we declare the linked list object then? See below:
type PMyLinkedList = ^TMyLinkedList; TMyLinkedList = object private data : TMyData; next : PMyLinkedList; public { Accessor } procedure SetNext(n: PMyLinkedList); function GetNext: PMyLinkedList; procedure SetData(d: TMyData); function GetData: TMyData; end;
See? It's similarly done. The fields are private and there are accessors. So, the data here is separated. If you'd like to use it for another form of data, you can rip it directly. :-) Now, there is a problem: How about if I'd like to use the linked list with multiple data types, do I have to copy and paste the same code and just change data : TMyData declaration to suit other data types as well? That's a quick and dirty solution, Sherlock! :-) We have a much cleaner way...
Here comes the usage of inheritance. Do you still remember that
suppose class A is the parent of class B and C.
When we declare a variable of type A, we can assign it to both
B and C as well, right? In case you forgot, review
here. Aha! Now, I just simply declare a dummy object as a parent
of all of my data types, say TMyObject. Then, I'd simply change
data : TMyData; to data : TMyObject; and voila!
The linked list is reusable for ALL of my program. I don't have
to copy and paste. Woo-hoo! Scream baby! Scream! :-)
Oh yes, the benefit of OOP is indeed too good to be true. Now, let's
see the declaration of the dummy class TMyObject:
Notice that we have a dummy integer field there called tag.
Well, the object has to have something inside, lest Pascal complaint.
If you're kinda stingier, just declare it as a byte ;-) so
that it cost you only one byte instead of two. Anyway, we'll gonna use
this field later on. Read on.
This approach is called genericization. (Yeah, I invent this
term myself :-). However, be warned that this approach somehow opposed
by OOP gurus. Anyway, we are learning. No need to learn extra bag of
hefty tricks now. So, be convenient with this approach in the mean time.
The next step is that to make your data types as the children of
this dummy class. So your TMyData class declaration will look
like this:
Of course, don't forget to declare TMyObject class
on the top of this declaration.
Likewise, if you'd like to be able to store linked list
within linked list, you have to declare your linked list
class as the child of TMyObject. Then, don't forget
to change the data declaration. See below:
Now, by doing the same thing for all of your data types,
you can store any data types in your linked list. Neat, huh?
Then, what's next? Do we need to build a constructor for this class?
Of course. Constructors are basically procedures to set up the object, right?
Objects need to have certain fields and/or other data set or pre-processed prior
to usage. Linked list will of course require the next field be initialized to
nil, right? So, we need to set it inside the constructor. Therefore,
add constructor Init; as a public method and it contains the following code:
Then, we have to define other operations as well: Add, delete, insert, and purge
as the methods of our linked list class. How can we add those? Next section please... :-)
Adding an element is simple, too. Still remember the scheme we used to
add a node? We can simply put it at the very end of the list, right? That is,
to add it after the last node. Where is the last node? It is the node that
has next field equals to nil. That's simple. If we have not
yet reach the last node, we can simply recurse the list down.
So, the code looks like this:
If you'd like to, you can have the Add method to handle TMyObject
instead of the linked list node pointer. You can easily do that:
The next thing you would do is to find a particular node. Hmm... this is
quite difficult as we now deals with TMyObject instead of TMyData.
How can we compare them then? Remember that the parent TMyObject is to
provide some abstraction about the data types so that all data types could fit into
the very same linked list code. So, since we now deals with some "abstract" thing,
we have to provide some abstraction on equality.
If you have learnt Java, you can quickly notice that we have equals function
in the Object class. That's what it does. It abstract the equalities out of
all possible children. So, we then modify our TMyObject as follows:
All children of TMyObject must override equals function and
supply the code to match the object. Why virtual? Doing this will make the
corresponding children method be called if it is inherited. In case you forgot,
review here.
Thus, we modify TMyData and TMyLinkedList accordingly:
Then, how can we compare the objects then? Look at the code below:
In abstract level, we are not certain whether two objects are equal, since we don't know
which child does TMyObject refer to. Moreover, in abstract level (i.e. TMyObject)
we cannot compare the contents of the two objects right away since the only thing that knows
how to compare its object contents is the child itself. But, the only thing for sure is that
if the addresses of both objects are the same, then those objects must be equal.
In a more specific data-types (i.e. the children), this can be too restricting because we can
say two objects as being equal if both contents are the same. So, the children data
types (or class) must inherit this equals method to detect the equality of the contents.
The code is as follows:
Since o is of type TMyObject, we must cast it down to make it the same type (TMyData
in this example). After it is the same type, then we can compare the contents. This is done analoguously
in TMyLinkedList
Aha! Since we, again, deal with abstract data field, to compare it, we must then
use the equals function. Then, we compare the next field normally.
Now, there is a problem. How can we be sure that the o parameter is indeed
of type TMyData or TMyLinkedList. We could have passed it the way around
and be legal right? Yes. This caveat can be disastrous. So, how can we deal with this?
Here comes the use of the tag field of TMyObject. We use this tag
to identify the class type. This tag field must be initialized by the constructor of each
object. Notice that each tag ID of each class must be unique so that you wont confuse it with
other class' tag ID. So, probably it's better to place the ID definition on the constant
declaration:
Then, we add a constructor in TMyObject and TMyData to initialize
this tag.
The contents is this:
Then, we modify TMyLinkedList's constructor too:
Having modified the constructors to incorporate the tags, we have
to once again modify our equals method to detect the tags as well:
Whew! I explain this to you in a gory detail. This is crucial to develop
a good OOP program. If you find this is tedious, you can always couple
the linked list with your data, which is a lot easier. However, you will
lose the flexibilities in plugging all data types for this linked list.
Okay. Now, since we have defined the equality function, we can find
a certain node easily. Check out the following Find function:
You may wonder the then Find := self else... part.
The keyword self is to return a pointer that point to the current
object. So, when the data matches, it will return the pointer of the node
holding that data. Otherwise, if we came to the last node (i.e. the next
field is nil), it will return nil. If we still have
next nodes, we simply recurse the find to the next node. Simple.
Of course you can modify it to return the pointer directly
to the data itself though. It's easy, too. Why don't you try that?
Inserting linked list node is as simple as Add. See
the following code:
Presto! How about delete? Unfortunately, deleting a node is
harder. Why? It is because we only have next pointer.
Upon deletion, you need to make sure its previous node's next
pointer no longer points to the deleted data. This is impossible
to do it "just as is". However, we can have a workaround.
We can use our Find function to find the node and
search for its previous node. Then, do the usual delete. Look
at the following code:
Ah! That's pretty standard, isn't it? Now, what if the
Data is at the head node? Hmm, you have to modify
the code. The really bad thing is that you have to do some
work around. It's not hard, but it's not trivial too.
Why don't you try that? BIG Hint: You can move the next pointer's
data to the head, then delete head's next pointer. Where should I
do this? Look at the if part. Which if? Guess. ;-)
The last method we'd like to discuss here is the
destructor. We should setup the destructor so that
it purges the linked list correctly. The idea is to
enable user to invoke just dispose(ll, Done);
and the whole linked list is destroyed. The destructor
Done is thus setup as follows:
Upon the call of dispose(next, Done);, it will
invoke the next's Done destructor. So,
it will recurse down until the last node. After the destruction,
you can optionally set the next field to nil.
Simple, right?
Using them is even simpler. After the declaration, you
can declare your object as follows:
This is the head of your linked list. You can add
data using Add method. However, bewarned that
if you do so, your head node will have an empty
data. For further info, see how Add actually
adds data. If you add data using Add method,
then you don't have to worry about the deletion of head
data in method Delete. So, it has pluses and minuses.
Depending on how you use them.
If you'd like to see more on how to use this linked list
object, you can refer to the next chapter,
chapter 6.
Is it possible for us to extend this linked list object into
a double linked list, but you don't want to rewrite the whole
code again and you don't want to lose the single linked list
as well. Yes, IT IS possible!! Thanks to the strength
of OOP methodology. Now the question is: How?
First, declare a double linked list class as a descendant
of the single linked list. Then, you would like to add a
prev node. The next node is already inherited
right?
Aha! All the methods are inherited as well. Why should
we declare the prev node as PMyLinkedList
instead of PMyDblList? Well, that is for compability purpose.
Because TMyLinkedList is the parent of TMyDblList,
you can assign prev to both PMyLinkedList AND
PMyDblList. If you declare prev as PMyDblList,
you can only assign it to PMyDblList. Still remember the
OOP philosophy? ;-)
Now, how can we override the parents' methods to suit
our need now? Hmm... let's see. Upon all the methods, you are no
longer need to inherit Find method as
it is still applicable. I think that the destructor Done
is still applicable too. So, you don't need to override it either.
Add method, Insert method, Delete method,
and the constructor need to be
overrided. Let's look at the constructor first. What do we need to
add in constructor Init here? We just want to initialize
prev to nil, right? Everything else is just the same.
So, the code is like this:
Wow, that's simple. The inherited Init; simply said that
we want to invoke the parent's Init method, which initialize
the next field. Then, we set prev field to nil.
We then have to place the ID for TMyDblList class under
the inherited Init statement. Why? Since the inherited
statement will set the tag to TMyLinkedList's ID.
Since we'd like to denote that this object is different than its
ancestor, we can simply set it to a different ID. The equals
function does not change, right? Yes, it does not change. Thanks to
the strength of OOP. Of course you need to define ID_TMyDblList
in your constant declaration. Easy right?
Of course you have to declare the Init constructor
as virtual at the parent and you have to redeclare it
in TMyDblList class. Otherwise, Pascal would complain. So, up to
now, our class declaration is as follows:
Don't forget to declare the Init constructor at TMyLinkedList
class as virtual too. Just remember this principle whenever you override
things.
Now, for the Add method. What do we need? Well, we need to fill in
the correct prev value too. How? Let's look at the original Add
method:
Hmm... we can use it, couldn't we? So, we can invoke inherited Add(theData);
too. Then, we can fill in the correct prev value. How?
Hmm... that's easy! Before invoking the parent's Add method, we need to
set the current next's prev field to theData. Why? It is because
theData is inserted before the current next field. Consequently,
theData's previous node is the current node (i.e. self). so that's what
the second statement does. Since setting the prev fields are done now, you can
invoke the parent's Add method to fill in the next field. Easy, right?
You may ask: How can we know which method to override? Well, we have to look into the
code. Treat it as if the parent's method is an ordinary procedure or function. The call
of inherited blah is merely a procedure or a function call to that parent's method.
You have to inspect whether or not you can use the parent's method. If you can, you have
to ask yourself what things need to be modified before or after that method call. That's
the statements you would add before or after the inherited call. If you decided
that you want to throw out parent's method, it's OK too. However, that may be a lot of work.
It's up to you then to experiment.
You have already seen two examples on how to override the parent's methods.
How about overriding Insert and Delete method? Hmm... I think
I'll leave it for you as an exercise.
I think that creating elements based on OOP is very
beneficial. You can derive other elements by just inheriting
the classes and override some method to customize. You don't
need to rewrite things too. You can see that building an OOP
elements is also simple. I hope that after this example,
you can start to actually design an application using
OOP methodologies. If you need some example about such,
stay tuned for the next chapters of this series.
Chapter 6 Roby Joehanes © 2001type
PMyObject = ^TMyObject;
TMyObject = object
tag: integer;
end;
type
PMyData = ^TMyData;
TMyData = object(TMyObject)
private
name : String;
score : byte;
public
{ Accessors }
procedure SetName(n: String);
function GetName: String;
procedure SetScore(n: byte);
function GetScore: byte;
end;
type
PMyLinkedList = ^TMyLinkedList;
TMyLinkedList = object(TMyObject)
private
data : TMyObject;
next : PMyLinkedList;
public
{ Accessor }
procedure SetNext(n: PMyLinkedList);
function GetNext: PMyLinkedList;
procedure SetData(d: TMyObject);
function GetData: TMyObject;
end;
constructor TMyLinkedList.Init;
begin
next := nil;
end;
Adding Elements
procedure TMyLinkedList.Add(theData: PMyLinkedList);
begin
if next <> nil then next^.Add(theData) else
begin
next = theData;
theData^.next = nil;
end;
end;
procedure TMyLinkedList.Add(theData: TMyObject);
var p : PMyLinkedList;
begin
if next <> nil then next^.Add(theData) else
begin
new(p, init);
p^.SetData(theData);
next = p;
p^.next = nil;
end;
end;
Node Equality
type
PMyObject = ^TMyObject;
TMyObject = object
private
tag: integer;
public
function equals(o : TMyObject): boolean; virtual;
end;
type
PMyData = ^TMyData;
TMyData = object(TMyObject)
private
name : String;
score : byte;
public
{ Accessors }
procedure SetName(n: String);
function GetName: String;
procedure SetScore(n: byte);
function GetScore: byte;
function equals(o : TMyObject): boolean;
end;
type
PMyLinkedList = ^TMyLinkedList;
TMyLinkedList = object(TMyObject)
private
data : TMyObject;
next : PMyLinkedList;
public
constructor init; virtual;
procedure Add(theData: TMyObject);
{ or procedure Add(theData: PMyLinkedList); depending on your choice }
{ Accessor }
procedure SetNext(n: PMyLinkedList);
function GetNext: PMyLinkedList;
procedure SetData(d: TMyObject);
function GetData: TMyObject;
function equals(o : TMyObject): boolean;
end;
function TMyObject.equals(o : TMyObject): boolean;
begin
equals := (addr(o) = addr(this));
end;
function TMyData.equals(o : TMyObject): boolean;
var d : TMyData;
begin
d := TMyData(o);
equals := ((d.name = data.name) and (d.score = data.score));
end;
function TMyLinkedList.equals(o : TMyObject): boolean;
var d : TMyLinkedList;
begin
d := TMyLinkedList(o);
equals := (d.getData.equals(data)) and (d.getNext = next));
end;
const
ID_TMyObject = 0;
ID_TMyData = 1;
ID_TMyLinkedList = 2;
type
PMyObject = ^TMyObject;
TMyObject = object
private
tag: integer;
public
constructor init; virtual;
function equals(o : TMyObject): boolean; virtual;
end;
type
PMyData = ^TMyData;
TMyData = object(TMyObject)
private
name : String;
score : byte;
public
constructor init; virtual;
{ Accessors }
procedure SetName(n: String);
function GetName: String;
procedure SetScore(n: byte);
function GetScore: byte;
function equals(o : TMyObject): boolean;
end;
constructor TMyObject.init;
begin
tag := ID_TMyObject;
end;
constructor TMyData.init;
begin
tag := ID_TMyData;
end;
constructor TMyLinkedList.Init;
begin
tag := ID_TMyLinkedList;
next := nil;
end;
function TMyObject.equals(o : TMyObject): boolean;
begin
equals := ((o.tag = tag) and (addr(o) = addr(this)));
end;
function TMyData.equals(o : TMyObject): boolean;
var d : TMyData;
begin
if o.tag = tag then
begin
d := TMyData(o);
equals := ((d.name = data.name) and (d.score = data.score));
end else equals := false;
end;
function TMyLinkedList.equals(o : TMyObject): boolean;
var d : TMyLinkedList;
begin
if o.tag = tag then
begin
d := TMyLinkedList(o);
equals := (d.getData.equals(data)) and (d.getNext = next));
end else equals := false;
end;
Finding a Node
function TMyLinkedList.Find(theData: TMyData): PMyLinkedList;
begin
if (data.equals(theData)) then Find := self else
begin
if (next <> nil)
then Find := next^.Find(theData)
else Find := nil;
end;
end;
Insert and Delete
procedure TMyLinkedList.Insert(theData: PMyLinkedList);
begin
theData^.SetNext(next);
next = theData;
end;
procedure TMyLinkedList.Delete(theData: TMyData);
var p, t: PMyLinkedList;
begin
p := Find(theData); { Find matched data }
if p <> nil then { If we find the data }
begin
t := self; { Start from ourself }
{ Find p's previous pointer }
while (t^.next <> p) and (t <> nil) do
t := t^.next;
{ If we find it, ... }
if t <> nil then
begin
t^.next := p^.GetNext; { Weave the pointer first }
p^.SetNext(nil); { Exclude p from the list }
dispose(p, done); { Delete p }
end;
end;
end;
Purging Elements: Doing Destructors
destructor TMyLinkedList.Done;
begin
if next <> nil then
begin
dispose(next, Done);
next := nil;
end;
end;
How To Use Them
var
head : PMyLinkedList;
Extending To Double Linked List
type
PMyDblList = ^TMyDblList;
TMyDblList = object(TMyLinkedList)
private
prev : PMyLinkedList;
end;
constructor TMyDblList.Init;
begin
inherited Init;
prev := nil;
tag := ID_TMyDblList;
end;
type
PMyDblList = ^TMyDblList;
TMyDblList = object(TMyLinkedList)
private
prev : PMyLinkedList;
public
constructor Init; virtual;
end;
procedure TMyLinkedList.Add(theData: PMyLinkedList);
begin
if next <> nil then next^.Add(theData) else
begin
next = theData;
theData^.next = nil;
end;
end;
procedure TMyDblList.Add(theData: PMyLinkedList);
begin
next^.prev := theData;
theData^.prev := self;
inherited Add(theData);
end;
Closing
Where to go
News Page
Pascal Lesson 3 index
Contacting Me