7

Introduction to Object Design and Use

Earlier we were introduced to the idea of external packages, references and scope. Objects are an advanced use of these three techniques to create a new data construction. There are reasons why we would want to create objects for our use.

  1. Reusable Code
  2. To create Application Programming Interfaces (API)
  3. To store subroutines as data constructions in the same construction as the data.
  4. To ease code maintenence
  5. To facilitate runtime loading of data and functions
  6. To permit the easy extension of our API without rewriting core code
We know that we can create a reference to a data object by using the slash in front of the variable which we want to refer to as follows:
$ref = \$myscalar
Perl also allows us to create references to objects without using a variable. This is called an anonymous reference. For a hash we can create an anonymous hash as follows:
$hash_ref = {Animal => "Dog", Breed=>"Irish Setter", Name=>"Joe"};
Let's look at how we might leverage this capability to our advantage

#!/usr/bin/perl -w
 use lib "/home/ruben/packagetest";
 use strict;
 use diagnostics;
 use Birds;
 
 my $outerref1= $Birds::ref;
 print "$$outerref1{size}\n";
 $$outerref1{size} = "Big";
 print "$$outerref1{size}\n";
 
 my $outerref2= $Birds::ref;
 print "$$outerref2{size}\n";
 print "Now get the subroutine\n";
 
 my $outerref3= Birds->new;
 print "$outerref3\n";
 my $size = $outerref3->{size};
 print "$size\n";
 my $outerref4= Birds->new;
 print "$outerref4\n";


Here we are storing some of our programming information in an external file called Birds.pm. The top of the program brings in 3 modules including lib module which specifies the alternative path that Perl should look for your modules.

We store our module Birds in the directory that lib describes. And then we bring our module into the program with the use Bird command. This command searches for Bird.pm in Perls path system (which is stored in a system array called @INC) and loads the file into the current program for accessibility. Call external files should end with a positive value (1 will do). Our external file declares a new namespace with the call package Birds. It is most useful to declare on package per file that has the same name as the file with a .pm extensions. Together this creates a module. When someone talks about a module in Perl, they are referring to a .pm file is a single package within.

package Birds;
 
 $ref = {
 	size => "small",
 	color => "blue",
 	feathers => "powdery",
 	genus => "Parot",
 	sounds => "Talk",
 };
 
 sub new{
 	my $ref2 = {
 		size => "small",
 		color => "blue",
 		feathers => "powdery",
 		genus => "Parot",
 		sounds => "Talk",
 	};
 	return $ref2
 }
 
 1;
 
 	


Now let's look at how the main program interacts with the module. The module contains two sections, one is a subroutine that returns a lexically scoped anonymous hash reference (my scoped to the function). And the other is an anonymous hash reference which is global to the namespace Birds.

Our program makes direct access of the first reference through the notation $Birds::ref. This notation accesses the the hash table of typeglobs which forms the symbols table namespace Birds. Remember that in addition to calling the symbol ref, we need to dereference the symbol is a datatype symbol ($ in this case). The store the anonymous hash reference stored in $Birds:ref into $outerref1. We then perform some standard manipulations on the hash as normal. The hash retains the the changes, even if we call for the hash again in a second time with $outerref2.

The output for the program looks like this:


ruben@ruben:/home/ruben/perl_course > ../packagetest/birds.pl
small
Big
Big
Now get the subroutine
HASH(0x80c11d8)
small
HASH(0x8104d24)

When we call the subroutine, it returns to us a reference to a hash. unlike the static hash in Birds defined in $ref, the anonymous hash returned by the subroutine is created at runtime and behaves completely differently. Each time the subroutine is called, a new hash is created. Each hash is a separate entity since they are my scoped into the subroutine. Changing a value in one has no effect on the subsequently created hashes. By our output, we see each hash is different with different Perl reference numbers.

It is with this on the fly creation of data constructions that allows Perl to create objects and produced object oriented code. But in addition to creating independent hashes, we need to also join them with the subroutines which manipulate the data contained within them. Perl provides a function for us to do this. This function is bless. Let's look at bless in action. This program does only one thing. It draws a new object from a module and prints out it's value.

#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use lib "/usr/local/ruben/perl_course";
 use file93;
 
 my $new_obj = file93->new;
 
 print "$new_obj\n";
 


The module it uses looks like this.


 
 package file93;
 
 sub new{
 	my $class = shift @_;
 	my $hashref={};
 	bless $hashref, $class;
 	return $hashref;
 }
 
 1
 


This module has one symbol within it called new. new is a subroutine which constructs the object for us. The object turns out to be nothing more than an anonymous hash, just like before. But now it's reference contains just a little more information contained within it. It knows which module called it.

ruben@ruben:/home/ruben/perl_course > file93.pl
file93=HASH(0x80c11d8)
ruben@ruben:/home/ruben/perl_course > 
The subroutine new is commonly called a constructor because it constructs the object. In addition, the module containing the constructor is generally called a class. A class in Perl is a module with a constructor. The function bless takes to arguments, a reference (usually a reference to a hash) and a class. Other than that, the constructor new is just like any other user defined subroutine. Arguments passed to it are stored in the system array @_ which accesses values of the parameter passed to it.

One thing which should be confusing you is the line
my $class = shift @_;
It would appear that the constructor new was called without any arguments in our main program with the line
my $new_obj = file93->new
In fact, the entire syntax might seem new. The operator -> is called the infixed operator. We can use it to deference a hash reference as follows:

$hash_ref->{key};
This is the same as

$$hash_ref{key};
When we use it with a module name, it passes the name of the package as the first argument in @_. It does this for us by magic. $_[0] is the name of the package. As we will see, it also does this for us with the object reference itself, making it easy to change the information stored in our anonymous hash.

Thus:
$ref = MODULE->new;

Passes the class Module as $_[0]. In our program, it is stored in $class and used in the function bless to help create our blessed object.


One of the nice things that we get from creating a blessed object is that all the subroutines now included in our module is now accessable through the object which is returned by our constructor. This example has one simple subroutine which adds data to the objects hash.

#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use lib "/usr/local/ruben/perl_course";
 use file94;
 
 my $new_obj = file94->new;
 
 print "$new_obj\n";
 while(1){
 	$new_obj->addinfo;
 }



 
 package file94;
 
 sub new{
 	my $class = shift @_;
 	my $hashref={};
 	bless $hashref, $class;
 	return $hashref;
 }
 
 sub addinfo{
 	my $obj = shift @_;
 	print "Add a Key==>";
 	my $key = <>;
 	chomp $key;
 	print "\nAdd a Value==>";
 	my $value = <>;
 	chomp $value;
 	$obj->{$key} = $value;
 	print "\nOK - added $key : $value to our object!\n";
 	print "You're object contains the following data now\n";
 	my $tmp;
 	for $tmp (keys(%$obj)){
 		print "Key $tmp\n";
 		print "Value $obj->{$tmp}\n";
 	}
 }
 
 1
 


A subroutine in a module or an object is called a method. The method addinfo keeps track of which object is calling it by shifting the entire object into it's domain. All the calls affecting the object are now done using the $obj-> syntax. Remember that each object holds it's own unique copy of it's data. Using the notation $obj-> necesitates us to use curly braces to access the hash itsef. Idealy, we would like to create an interface for all the elements of the object without the use of the curly braces. If we are using predifined fields within our object, we can do just that by creating methods with the same names as our fields which interact with the data for us.

package file95;
 
 sub new{
 	my $class = shift @_;
 	my $quest={
 		_questions =>{},
 	};
 	bless $quest, $class;
 	return $quest;
 }
 sub questions{
 	my $obj = shift @_;
 	my $questref = shift @_;
 	return $obj->{_questions} if !defined $questref;
 	$obj->{_questions} = $questref;
 }
 	
 sub addinfo{
 	my $obj = shift @_;
 	print "Add a Key==>";
 	my $key = <>;
 	chomp $key;
 	print "\nAdd a Value==>";
 	my $value = <>;
 	chomp $value;
 	$obj->{$key} = $value;
 	print "\nOK - added $key : $value to our object!\n";
 	print "You're object contains the following data now\n";
 	my $tmp;
 	for $tmp (keys(%$obj)){
 		print "Key $tmp\n";
 		print "Value $obj->{$tmp}\n";
 	}
 }
 
 1
 


In this module, we create a method questions which is used to access the data in questions. Fundementally, it does just two things, enters a set of questions or returns a set of questions. The first thing the method does in absorb the object so that it can manipulate the data witin. The second thing it does is shift the next argument from the @_ array. The theory is that te user is instructed that to load questions into our module, he writes a program which creates a hash of questions and answers which is sent as a reference to the obj->questions method.

In order to know whether or not the user of our module passed the questions to us, or whether he wants the questions returned to us, be check the second value that the infix operator send to us to see if it is defined or has data sent to it. If no data is sent, we assume the user of our module is requesting the questions currently stored within the object. Otherwise, if the user has sent us questions, in the form of a hash reference, then we store them in our object. Now the user can create a program using our module as so....

#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use lib "/usr/local/ruben/perl_course";
 use file95;
 my($tmp, $quest, $ans, %pairs, $quest_ref);
 my $new_obj = file95->new;
 
 for $tmp (1..5){
 	print "Question ==>";
 	$quest = <>;
 	print "\nAnswer ==>";
 	$ans = <>;
 	$pairs{$quest}=$ans;
 }
 
 $new_obj->questions(\%pairs);
 
 $quest_ref = $new_obj->questions;	
 
 for $tmp (keys %$quest_ref){
 	print "$tmp\n";
 	print "$$quest_ref{$tmp}\n";
 }


In this case, the user properly uses the questions method. The line:
$new_obj->questions(\%pairs);
Enters the questions into our object which is the value to the objects key {_questions}. He then properly retrieves the information with the statment
$quest_ref = $new_obj->questions;
This retrieves the hash reference which is manipulated as normal.


Once we have manipulated some of the data in our objects ainonymous hash via the accessory methods we created, it becomes increasingly easy to write complex code for our users or our modules to reuse.
package file96;
 
 sub new{
 	my $class = shift @_;
 	my $quest={
 		_questions =>{},
 	};
 	bless $quest, $class;
 	return $quest;
 }
 sub questions{
 	my $obj = shift @_;
 	$questref = shift @_;
 	return $obj->{_questions} if !defined $questref;
 	$obj->{_questions} = $questref;
 }
 
 sub putquest{
 	my $obj = shift @_;
 	my $home = $ENV{HOME};
 	 my ($tmp, $ans);
 	 if ( -e "$home"."/quiz.dbf"){ #check if the database exists
 	 	open FH, ">>$home"."/quiz.dbf" or die "$!\n";
 	 }else{
 		open FH, ">>$home"."/quiz.dbf" or die "$!\n";
 	 }
 	 my $questref = $obj->questions;#retrieve questions
 	 for $tmp (keys %$questref){
 		 $ans = $questref->{$tmp};
 		 chomp $tmp;
 		 chomp $ans;
 		 print FH "$tmp\t";
 		 print FH "$ans\n";
 	 }
 } 
 
 	 
 
 
 
 	
 sub addinfo{
 	my $obj = shift @_;
 	print "Add a Key==>";
 	my $key = <>;
 	chomp $key;
 	print "\nAdd a Value==>";
 	my $value = <>;
 	chomp $value;
 	$obj->{$key} = $value;
 	print "\nOK - added $key : $value to our object!\n";
 	print "You're object contains the following data now\n";
 	my $tmp;
 	for $tmp (keys(%$obj)){
 		print "Key $tmp\n";
 		print "Value $obj->{$tmp}\n";
 	}
 }
 
 1
 


Here we enter a new method, putquest, which simplifies the addition of questions to our database. We placed the complixity of problem in the module, and the user now only adds one line of code to his program when adding questions to the database.
#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use lib "/usr/local/ruben/perl_course";
 use file96;
 my($tmp, $quest, $ans, %pairs, $quest_ref);
 my $new_obj = file96->new;
 
 for $tmp (1..5){
 	print "Question ==>";
 	$quest = <>;
 	print "\nAnswer ==>";
 	$ans = <>;
 	$pairs{$quest}=$ans;
 }
 
 
 
 $new_obj->questions(\%pairs);
 
 $quest_ref = $new_obj->questions;	
 
 for $tmp (keys %$quest_ref){
 	print "$tmp\n";
 	print "$$quest_ref{$tmp}\n";
 }
 $new_obj->putquest;


In our design, we may want to keep track of the size of our questions hash to facilitate the addion or removal of questions. Let's add a _size field too our object create the necessesary accessory method for our API. When we add the $obj->{_size} data to our program, we want to consider where it is being used and how it is to be used in our module. We also have to consider if we want to make '{_size}' a public access part of our object, or an internal part of our object. Since I want size to keep track of the size of our question data, I'll decided design it as non-public access.

Other programming languages have two types of data in an object or class, private and public. The private data is not within the scope of the programmer using the class. Public data is available for the user for manipulation. Perl doesn't have this convention. Instead, we depend on documentation to tell the user what is available and how to use it. If they go outside of your documented functionality, the consequences are on them.

package file97;
 
 sub new{
 	my $class = shift @_;
 	my $quest={
 		_questions =>{},
 		_size => 0,
 	};
 	bless $quest, $class;
 	return $quest;
 }
 sub questions{
 	my $obj = shift @_;
 	$questref = shift @_;
 	return $obj->{_questions} if !defined $questref;
 	my(@keys) = keys(%questref);
 	$count = @keys;
 	$obj->size($count);
 	$obj->{_questions} = $questref;
 }
 
 sub putquest{
 	my $obj = shift @_;
 	my $home = $ENV{HOME};
 	my ($tmp, $ans);
 	 if ( -e "$home"."/quiz.dbf"){ #check if the database exists
 	 	open FH, ">>$home"."/quiz.dbf" or die "$!\n";
 	 }else{
 		open FH, ">>$home"."/quiz.dbf" or die "$!\n";
 	 }
 	 my $questref = $obj->questions;#retrieve questions
 	 my $size_sent = $obj->size;	 
 	 for $tmp (keys %$questref){
 		 $ans = $questref->{$tmp};
 		 chomp $tmp;
 		 chomp $ans;
 		 print FH "$tmp\t";
 		 print FH "$ans\n";
 		 delete $questref->{$tmp};
 		 $obj->size(--$size_sent);
 		 
 	 }
 	 close FH;
 	 return $size_sent;
 } 
 
 sub size{
        my $obj = shift @_;
        my $size = shift @_;
        if (!defined $size){
 	       $size = $obj->{_size};
 	       return $size;
        }
        $obj->{_size} = $size;
 }
 
 
 
 
 	
 sub addinfo{
 	my $obj = shift @_;
 	print "Add a Key==>";
 	my $key = <>;
 	chomp $key;
 	print "\nAdd a Value==>";
 	my $value = <>;
 	chomp $value;
 	$obj->{$key} = $value;
 	print "\nOK - added $key : $value to our object!\n";
 	print "You're object contains the following data now\n";
 	my $tmp;
 	for $tmp (keys(%$obj)){
 		print "Key $tmp\n";
 		print "Value $obj->{$tmp}\n";
 	}
 }
 
 1
 


After creating the new field in the anonymous hash, we create the customary access method. But now I have to decide when it is apropriate to set the size of my questions. Ideally, everytime we add a pair of questions and answers to our questions hash, we would itinerate the size. Likewise, any time we delete a question, for whatever reason, we want to decrease the integer stored in {_size}.
#!/usr/bin/perl -w
 use strict;
 use diagnostics;
 use lib "/usr/local/ruben/perl_course";
 use file97;
 my($tmp, $quest, $ans, %pairs, $quest_ref);
 my $new_obj = file97->new;
 
 for $tmp (1..5){
 	print "Question ==>";
 	$quest = <>;
 	print "\nAnswer ==>";
 	$ans = <>;
 	$pairs{$quest}=$ans;
 }
 
 
 
 $new_obj->questions(\%pairs);
 
 $quest_ref = $new_obj->questions;	
 
 for $tmp (keys %$quest_ref){
 	print "$tmp\n";
 	print "$$quest_ref{$tmp}\n";
 }
 $new_obj->putquest;



NEXT: Anonymous Reference and Object Orientation