7 |
Introduction to Object Design and Use |
#!/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"; |
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; |
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.
#!/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 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.
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; |
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; |