Defining and Using Objects

A class usually provides a method for creating an object as well as methods for defining the behavior of an object. Although you may name the creation method whatever you wish in most Perl dialects, in .NET it must be named new in honor of both C++ and Java, where it is likewise mandatory.

The data contained in an object is composed of many pieces. A Student object, for example, might contain a NAME, a MAJOR, and a set of COURSES. One way to model this in Perl is to create an anonymous hash and then retrieve items from it. See the folder Object.


% type object.pl
#
#   object.pl
#
$student = {
    NAME => Mike,
    MAJOR => Math,
    COURSES => [Calc, Trig, DiffE ]
};
print $student->{NAME},"
";                 # the name
print $student->{MAJOR},"
";                # the major
print "@{$student->{COURSES}}", "
";        # the courses
print "@{$student->{COURSES}}[0]", "
";     # 0th course
% perl object.pl
Mike
Math
Calc Trig DiffE
Calc
%

Constructing Objects

The above code demonstrates a way to create a set of related data. But an object should be constructed more formally and in such a way that it knows to which class it belongs. In particular, there should be a way to create as many new Student objects as an application requires. What is needed is correct packaging. In order to do this, we create a file whose first line is

package Student;

This is the start of what ultimately will be the creation of a new data type named Student that will have an entire set of methods describing the behavior of a Student object. In this file, we place a method for the creation of a Student object. The code in this method will look similar to the code above. An application could use this method as many times as it wished in order to create as many Student objects as it needed. Methods to implement Student behaviors are required as well. We will see them shortly. Here's a version of Student.pm. See the folder UseStudent.


% type Student.pm
#
#   Student.pm
#
package Student;
sub new
{
    my($pkg, $name, $major, @courses) = @_;
    my $s = {   NAME => $name,
                MAJOR => $major,
                COURSES => [ @courses ]
            };
    bless $s, $pkg;         # see later for details
    return $s;
}
1;
%

The first method that we illustrate is called new. Its purpose is to construct a new Student object. This method must be named new in .NET. Further, this method is typically referred to as the constructor for the Student class because it is used to construct new Student objects. The intent is to invoke the constructor and pass to it the arguments it needs to build a Student object. The Perl syntax for invoking the constructor is shown below. For reasons that will become clear shortly, we also pass the package name as the first argument.

@courses = (Calc, Trig, DiffE);
$s1 = Student::new(Student, Mike, Math, @courses);

Although the notation above can be used, there is an alternative and equivalent way in which to invoke the Student::new method.

$s1 = new Student(Mike, Math, @courses);

The alternative method is closer to the C++ and Java syntax. When you use this form, Perl translates it to the original form. The following two statements are thus equivalent.

$s1 = new Student(Mike, Math, @courses);
$s1 = Student::new(Student, Mike, Math, @courses);

There is also a third notation that is equivalent to the first two.

$s1 = Student->new(Mike, Math, @courses);

The new method creates a reference to an anonymous hash and then fills the hash with the parameters. Before this method returns the reference, it adds to the reference an additional property—the package name—in this case, Student. This is accomplished with the bless function. Now the reference is a blessed hash reference and not simply a hash reference.

bless $s, $pkg;

This additional property will be necessary when we call methods on the hash and when we discuss inheritance. An application using a Student object might look like this. See the folder UseStudent.


% type useStudent.pl
#
#    useStudent.pl
#
use Student;
@courses = qw(Calc Trig DiffE);
$s1 = new Student(Mike, Math, @courses);
print $$s1{NAME}, "
";
print $s1->{MAJOR}, "
";
print "@{$$s1{COURSES}}", "
";
print @{$s1->{COURSES}}[0], "
";
% perl student1.pl
Mike
Math
Calc Trig DiffE
Trig
%

Information Hiding

Although the above code works fine, the retrievals leave a few things to be desired. First, many of the retrievals use a cumbersome syntax. We would like to avoid this. Second, the retrievals above place a burden on the writer of the code. The writer must be familiar with the field names. For example, see if you can determine why the code below would produce incorrect results.

use Student;
@courses = (Calc, Trig, DiffE);
$s1 = new Student(Mike, Math, @courses);
print $$s1{name}, "
";
print $s1->{name}, "
";
print "@{$$s1{COURCES}}", "
";
print @{$s1->{COURCES}}[0], "
";

The reason is that the developer has misspelled the last two indices. It should be COURSES, not COURCES. Users of the Student.pm file, that is, users of the Student class, should not have to worry about any information inside the Student.pm file, let alone the spelling of any of the keys.

These problems may be overcome by writing methods that deliver object data rather than allowing direct access to the data. The disallowance of direct access to object data is called information hiding. Most object-oriented languages generate a compiler error when direct access to hidden data is attempted. Perl just expects you to “play by the rules,” as it has no such feature as hidden data.

Accessor and Mutator Methods

A good class is expected to provide methods to retrieve data from an object. These methods are called accessor methods. Likewise, a good class is expected to provide methods to alter the state of an object. These methods are called mutator methods. Other methods might simulate other behavior. For example, in the Student class, there could be a method that determines how many courses a student is taking.

The Student class should have methods that allow the setting and getting of object data. Functionality like setname and getname should be supported.

use Student;
$s1 = new Student(Mike, Math, Trig, Calc);
$s1 -> setname(Michael);
$name1 = $s1->getname();

When a method is called on a blessed reference, such as in the last two lines above, Perl knows to look in the appropriate .pm file to find the correct method. In reality, Perl turns the last two calls above into

Student::setname($s1, Michael);
Student::getname($s1);

so that the reference is always passed as the first argument to the method in question. Here is the revised Student.pm file. This file contains the methods getname() and setname().

package Student;
sub new
{   my($pkg, $name, $major, @courses) = @_;
    my $s =  {        NAME => $name,
                      MAJOR => $major,
                      COURSES => [ @courses ]
              };
    bless $s, $pkg;
    return $s;
}
sub getname
{   my ($ref) = shift;            # store reference
    $ref->{NAME};                 # return the name
}
sub setname
{   my ($ref, $name) = @_;        # store ref and new name
    $ref->{NAME} = $name;         # assign new name
}
1;

Other Instance Methods

A real class would have some other methods as well. Here are some possibilities.

@courses = $s1 -> getcourses();
$s1 -> addcourses(Geometry, Algebra);
$numcourses = $s1 -> numcourses();
$course = $s1 -> getcourse(1);

Here is how you might write these additional methods.

sub getcourses
{
    my($ref) = @_;                      # ref to student
    @{$ref->{COURSES}};                 # all the courses
}
sub addcourses
{
    my($ref, @courses) = @_;            # ref + new courses
    push( @{$ref->{COURSES}}, @courses);# push onto list
}
sub numcourses
{
    my($ref) = @_;                      # ref to student
    scalar(@{$ref->{COURSES}});         # number of courses
}
sub getcourse
{
    my($ref, $num) = @_;                # ref + index
    @{$ref->{COURSES}}[$num];           # return a course
}

In each method, a reference to a student is automatically passed in as the first argument. In each case,

$ref->{COURSES}

is a reference to a reference to the courses, and thus,

@{$ref->{COURSES}}

is the array of courses. The benefit of each method is that the difficult Perl constructions are encapsulated within the methods. Users of these methods are freed from details of the encapsulated code.

Writing an Accessor and a Mutator as One Method

Remember that an accessor method retrieves the value of some object data.

$s1 = new Student(Susan, Dynamics, Statics, Art);
$name1 = $s1->getname();

Also, recall that a mutator method sets the value for some object data.

$s1 = new Student(Susan, Dynamics, Statics, Art);
$s1->setname("Soozie");

Some Perl programmers use the same function to both get and set object data. In order to do this, the programmer takes advantage of the fact that in setting object data, there is one real argument passed to the method, while in getting the object data, there are zero real arguments sent to the method. Let's suppose we call this method name. The code for invoking both getting and setting the name would be

$s1 = new Student(Susan, Dynamics, Statics, Art);
$s1->name("Soozie");              # set the name
$thename = $s1->name();           # get the name

You can see that in setting the name, there is no need to capture the returned value from this method, although if you wanted, you could write the function so that it returned the old value of the name. Here's the code for the dual-purpose name method.

sub name
{
    my $ref = shift;
    if ( @_ )         # if another argument
    {                 # then must be setting the name
          my($oldname) = $ref->{NAME};
          $ref->{NAME} = shift;
          $oldname;
    }
    else              # if no other args
    {                 # then must be getting the name
          $ref->{NAME};
    }
}

As usual, the first argument is the reference and it is shifted into $ref. Then @_ is tested to see if any arguments remain. If there is one, the old name is tucked away in $oldname, the new name is shifted into the hash, and $oldname is returned. If there are no remaining arguments, the current name is returned.

If name is invoked with an argument, it could be used in two ways.

#   simply set name and ignore the returned value.
#
$s1->name("Michael");
#
#   set the name but remember the previous name
#
$oldname = $s1->name("Michael");

Destructors

Some classes may use resources when objects are constructed. For example, a class that encapsulates the details for a File object might create a file handle when a File object is constructed. It is essential that this resource be given back to the system when the object that used it is no longer in use. Toward this end, Perl executes the special subroutine DESTROY if you provide it in your class. Object-oriented languages refer to this method as the destructor. It must be named DESTROY, and it will be called automatically whenever the space for an object is reclaimed.

In the previous Student example, a DESTROY method is not needed because no resources are used. If we provided one, it would still be executed each time a blessed reference was no longer in use. Here's an example of the DESTROY method. Keep in mind that most of the work in this particular DESTROY method is for illustrative purposes. Strictly speaking, it is not necessary for this class. See the folder Destroy.


sub DESTROY
{
    my ($ref) = shift;
    print "destroying ";
    print $ref -> {NAME}, "
";
}

% type destroy.pl
#
#   destroy.pl
#
use Student;
sub dosomething
{
    my($s2) = new Student(Jane, Math, Trig);
}
$s1 = new Student(Mike, Math, Trig, Calc);
dosomething();
$s3 = new Student(Peter, Math, Trig, Calc);
% destroy.pl
destroying Jane
From destructor: 1 objects
destroying Mike
From destructor: 1 objects
destroying Peter
From destructor: 0 objects
%

Notice that the objects are destroyed when zero references point to them or when the program terminates. The object defined within the subroutine is removed from memory when the subroutine terminates. Thus, it is the first object for which the DESTROY method is called. Objects created within the same block of code are destroyed in the opposite order of creation.

Class Data and Class Methods

Each object has its own set of data. However, some programs may require that there be data of a class rather than of an object. This kind of data exists on a per-class basis rather than on a per-object basis. In .NET, such data may also be referred to as static data (C#) or shared data (VB.NET). If your application contains 10 student objects, there would be only be one instance of any class data.

Class data can be realized by using my variables at file scope. For example, suppose we wish to track the number of objects currently allocated at any given time in a program. We would make the following additions to the Student.pm file.

  1. Add a my variable.

    my $count = 0;
    
  2. Add the following line to the constructor.

    $count++;
    
  3. Add the following line to the destructor.

    $count--;
    

At this point we might also want to add a method that can return the number of objects that have been created. Such a method is shown below.

sub howmany
{
    return $count;
}

The interesting thing about the howmany method is that it does not operate on objects but on the class. Here is a program that uses the howmany method. Notice how it is invoked compared to how the other methods are invoked. See the folder Count.


Student::howmany()          # called upon the class
print $s3->getname();       # called upon an object

% type count.pl
#
#   count.pl
#
use Student;
sub dosomething
{
    my $s2 = new Student(Jane, Math, Trig);
    print Student::howmany(), " objects
";
}
$s1 = new Student(Mike, Math, Trig, Calc);
print Student::howmany(), " objects
";
dosomething();
$s3 = new Student(Peter, Math, Trig, Calc);
print Student::howmany(), " objects
";
% perl count.pl
1 objects
2 objects
destroying Jane
From destructor: 1 objects
2 objects
destroying Mike
From destructor: 1 objects
destroying Peter
From destructor: 0 objects
%

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.65.247