Linked List using OOP

An Example on OOP




Welcome

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.

 

Declarations

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:

type
   PMyObject = ^TMyObject;
   TMyObject = object
                  tag: integer;
               end;

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:

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;

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:

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;

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:

constructor TMyLinkedList.Init;
begin
   next := nil;
end;

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 Elements

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:

procedure TMyLinkedList.Add(theData: PMyLinkedList);
begin
   if next <> nil then next^.Add(theData) else
   begin
      next = theData;
      theData^.next = nil;
   end;
end;

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:

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

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:

type
   PMyObject = ^TMyObject;
   TMyObject = object
                  private
                     tag: integer;
                  public
                     function equals(o : TMyObject): boolean; virtual;
               end;

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:

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;

Then, how can we compare the objects then? Look at the code below:

function TMyObject.equals(o : TMyObject): boolean;
begin
   equals := (addr(o) = addr(this));
end;

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:

function TMyData.equals(o : TMyObject): boolean;
var d : TMyData;
begin
   d := TMyData(o);
   equals := ((d.name = data.name) and (d.score = data.score));
end;

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

function TMyLinkedList.equals(o : TMyObject): boolean;
var d : TMyLinkedList;
begin
   d := TMyLinkedList(o);
   equals := (d.getData.equals(data)) and (d.getNext = next));
end;

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:

const
    ID_TMyObject     = 0;
    ID_TMyData       = 1;
    ID_TMyLinkedList = 2;

Then, we add a constructor in TMyObject and TMyData to initialize this tag.

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;

The contents is this:

constructor TMyObject.init;
begin
   tag := ID_TMyObject;
end;
constructor TMyData.init;
begin
   tag := ID_TMyData;
end;

Then, we modify TMyLinkedList's constructor too:

constructor TMyLinkedList.Init;
begin
   tag := ID_TMyLinkedList;
   next := nil;
end;

Having modified the constructors to incorporate the tags, we have to once again modify our equals method to detect the tags as well:

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;

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.

Finding a Node

Okay. Now, since we have defined the equality function, we can find a certain node easily. Check out the following Find function:

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;

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?

 

Insert and Delete

Inserting linked list node is as simple as Add. See the following code:

procedure TMyLinkedList.Insert(theData: PMyLinkedList);
begin
   theData^.SetNext(next);
   next = theData;
end;

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:

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;

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. ;-)

 

Purging Elements: Doing Destructors

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:

destructor TMyLinkedList.Done;
begin
   if next <> nil then
   begin
      dispose(next, Done);
      next := nil;
   end;
end;

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?

 

How To Use Them

Using them is even simpler. After the declaration, you can declare your object as follows:

var
   head : PMyLinkedList;

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.

 

Extending To Double Linked List

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?

type
   PMyDblList = ^TMyDblList;
   TMyDblList = object(TMyLinkedList)
                   private
                      prev : PMyLinkedList;
                end;

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:

constructor TMyDblList.Init;
begin
   inherited Init;
   prev := nil;
   tag := ID_TMyDblList;
end;

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:

type
   PMyDblList = ^TMyDblList;
   TMyDblList = object(TMyLinkedList)
                   private
                      prev : PMyLinkedList;
                   public
                      constructor Init; virtual;
                end;

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:

procedure TMyLinkedList.Add(theData: PMyLinkedList);
begin
   if next <> nil then next^.Add(theData) else
   begin
      next = theData;
      theData^.next = nil;
   end;
end;

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?

procedure TMyDblList.Add(theData: PMyLinkedList);
begin
   next^.prev := theData;
   theData^.prev := self;
   inherited Add(theData);
end;

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.

 

Closing

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.

 


Where to go

Chapter 6
News Page
Pascal Lesson 3 index
Contacting Me


Roby Joehanes © 2001