Online Documentation Server
 ПОИСК
ods.com.ua Web
 КАТЕГОРИИ
Home
Programming
Net technology
Unixes
Security
RFC, HOWTO
Web technology
Data bases
Other docs

 


 ПОДПИСКА

 О КОПИРАЙТАХ
Вся предоставленная на этом сервере информация собрана нами из разных источников. Если Вам кажется, что публикация каких-то документов нарушает чьи-либо авторские права, сообщите нам об этом.




Chapter 5

Object-Oriented Programming in Perl


CONTENTS


This chapter covers the object-oriented programming (OOP) features of Perl. You'll see how to construct objects in Perl as well as how to use the OOP features offered by Perl. You'll also learn about inheritance, overriding of methods, and data encapsulation.

Introduction to Modules

A module is also referred to as a package. Objects in Perl are based on references to data items within a package. An object in Perl is simply a reference to something that knows which class it belongs to. For more information, you can consult the perlmod and perlobj text files at http://www.metronet.com. These files are the primary source of information on the Internet about Perl modules.

When performing object-oriented programming with other languages, you declare a class and then create (instantiate) objects of that class. All objects of a particular class behave in a certain way, which is governed by the methods of the class to which the object belongs. You create classes by defining new ones or by inheriting properties from an existing class.

For programmers already familiar with object-oriented principles, this will all seem familiar. Perl is, and pretty much always has been, an object-oriented language. In Perl 4, the use of packages gave you different symbol tables from which to choose your symbol names. In Perl 5, the syntax has changed a bit, and the use of objects has been formalized somewhat.

The Three Important Rules

The following three declarations are extremely important to understanding how objects, classes, and methods work in Perl. Each is covered in more detail in the rest of the chapter.

  • A class is a Perl package. The package for a class provides the methods for objects.
  • A method is a Perl subroutine. The only catch with writing methods is that the name of the class is the first argument.
  • An object in Perl is a reference to some data item within the class.

Classes in Perl

This point is important enough to repeat: A Perl class is simply a package. When you see a Perl document referring to a class, think package. Also, both package and module mean the same thing. For C programmers, it's easy to use :: notation for classes and -> for pointing to structure elements and class members.

One of the key features of OOP in any object-oriented language is that of inheritance. This is where new classes can be created by adding new features to existing classes. The inheritance feature offered by Perl is not the same as you would expect in other object-oriented languages. Perl classes inherit methods only. You have to use your own mechanisms to implement data inheritance.

Because each class is a package, it has its own name space with its own associative array of symbol names. Therefore, each class can have its own independent set of symbol names. As with package references, you can address the variables in a class with the ' operator. Therefore, members of a class are addressed as $class'$member. In Perl 5, you can use the double colon instead of ' to get the reference. Thus, $class'member is the same as $class::$member.

Creating a Class

This section covers the requisite steps to take when you create a new class. This chapter covers the semantics in the creation of a very simple class, called Invest, for printing the required parts of a simple Java application source code file. No, you will not become a Java expert, nor does this package require you to have any experience in Java. The concept of creating a class is what you're concerned with. For example, this chapter could just as easily have been on creating a phone book application, but how many such examples have you seen to date in books? You'll use a different example this time.

First of all, you need to create a package file called Cocoa.pm. The .pm extension is the default extension for packages; it stands for Perl Module. A module is a package, and a package is a class for all practical purposes. Before you do anything else to the file, place a 1; in the file. As you add more lines to the package file, make sure you have 1; as the last line of this file. The basic structure of the file is shown in Listing 5.1.


Listing 5.1. The package template.
 1 package Cocoa;
 2 #
 3 # Put "require" statements in for all required,imported packages
 4 #
 5
 6 #
 7 # Just add code here
 8 #
 9
10 1;   # terminate the package with the required 1;

It's important that you remember to always keep the required 1; line as the last of the package file. This statement is required for all packages in Perl.

Now you're ready to add your methods to this package and make this a class. The first method you would probably want to add is the new() method, which should be called to create a new object. The new() method is the constructor for the object.

Blessing a Constructor

A constructor is a Perl subroutine in a class that returns a reference to something that has the class name attached to it. Connecting a class name with a reference is referred to as blessing an object. The function to establish this connection is bless. Here's the syntax for the bless function:

bless YeReference [,classname]

YeReference is the reference to the object being blessed. classname is optional and specifies the name of the package from which this object will have methods. If classname is not specified, the name of the currently used package is used instead. Thus, the way to create a constructor in Perl is to return a reference to an internal structure that has been blessed into this class. The initial Cocoa.pm package is shown in Listing 5.2.


Listing 5.2. The first pass at the new() function.
1 package Cocoa;
2
3 sub new {
4     my $this = {};  # Create an anonymous hash, and #self points to it.
5     bless $this;       # Connect the hash to the package Cocoa.
6     return $this;     # Return the reference to the hash.
7     }
8
9 1;

The {} constructs a reference to an empty hash. The returned value to this hash is assigned to the local variable $this. The bless() function takes that reference to $this and tells the object it references that it's now Cocoa and then returns the reference.

The returned value to the calling function now refers to this anonymous hash. On returning from the new() function, the $this reference is destroyed, but the calling function keeps a reference to this hash. Therefore, the reference count to the hash will not be zero, and Perl keeps the hash in memory. You do not have to keep the hash in memory, but it's nice to have it around for reference later.

To create an object, you make a call like this one:

$cup = new Cocoa;

The code is Listing 5.3 shows you how to use this package to create the constructor.


Listing 5.3. Using the Cocoa class.
1 #!/usr/bin/perl
2 push (@Inc,'pwd');
3 use Cocoa;
4 $cup = new Cocoa;

The first line refers to the location of the Perl interpreter to use. Your Perl interpreter may be located at /usr/local/bin/perl or wherever you installed it.

In line 2, the local directory is added to the search path in @Inc for the list of paths to use when the Perl interpreter is looking for a package. You can create your module in a different directory and specify the path explicitly there. Had I created the package in /home/khusain/test/scripts/, line 2 would read as this:

push (@Inc,"/home/khusain/test/scripts");

In line 3 you include the package Cocoa.pm to get all the functionality in your script. The use statement asks Perl to look in the @Inc path for a file called Cocoa.pm and include it in the copy of the source file being parsed. The use statement is required if you want to work with a class.

Line 4 creates the Cocoa object by calling the new function on it. Now comes the beautiful (yet confusing and powerful) part of Perl. There is more than one way to do this. You can rewrite line 3 as this:

$cup = Cocoa->new();

Or if you are a C-programming hack, you can use the double colons (::) to force the function new() from the Cocoa package. Thus, line 4 could also be written as this:

$cup = Cocoa::new();

There is nothing preventing you from adding more code in the constructor than what is shown here. For this Cocoa.pm module, if you would like to print a disclaimer when each object is created, you can. For example, you can add statements like these in the constructor for debugging purposes:

print "Hey! I am alive" if ($debuglevel > 1);

This way, you can set a variable $debuglevel to a numeric value of  2 or greater in your program to display the debug message shown every time a new Cocoa object is created. Usually, you would like to initialize variables in a constructor before any processing is done with the object. For example, if the object you are constructing will be writing a log to disk, you would like to open the file it's writing to in the constructor. (Of course, you would also have to remember to close the file when the object is destroyed by placing the close() function call in the destructor.)

Here's what the constructor looks like for the Cocoa.pm module:


Listing 5.4. Expanding the constructor.
1 sub new {
2     my $this = {};
3     print "\n /* \n ** Created by Cocoa.pm \n ** Use at own risk";
4     print "\n ** Did this code even get past the javac compiler? ";
5     print "\n **/ \n";
6     bless $this;
7     return $this;
8     }

The output from running the test script (called testme) on this bare bones class would look like this:

/*
** Created by Cocoa.pm
** Use at own risk
** Did this code even get past the javac compiler?
**/

Now, regardless of which of these three methods you used to create the Cocoa object, you should see the same output.

Some comments have been added at the start of the file with some print statements. You can just as easily call other functions in or outside of the package to get more initialization functionality. You should allow any given class to be inherited, however. You should be able to call the new operator with the class name as the first parameter. This ability to parse the class name from the first argument causes the class to be inherited. Thus, the new function becomes more or less like the function shown in Listing 5.5.


Listing 5.5. The improved new() function with class name recognition.
1 sub new {
2     my $class = shift;        # Get the request class name
3     my $this = {};
4     bless $this, $class       # Use class name to bless() reference
5     $this->doInitialization();
6     return $this;
7 }

However, this method will force your class users to make calls in one of three ways:

  • Cocoa::new()
  • Cocoa->new()
  • new Cocoa;

What if you wanted to use a reference to the object instead, such as $obj->new()? The doInitialization() method used will be of whatever $class the object is blessed into. Listing 5.6 uses the function call ref() to determine if the class exists per se. The ref() function returns true if the item passed to it is a reference and null if not a reference. In the case of classes, the true value returned from the ref() function is the name of the class.


Listing 5.6. The new() function with the capability to inherit classes.
 1 sub new {
 2     my $this = shift;                # Get the class name
 3     my $class = ref($this) || $this;
 
   # If class exists, use it  else use reference.
 4     my $this = {};
 5
 6     bless $this, $class
 7     $this->doInitialization();
 8
 9     return $this;
10 }

Within the class package, the methods typically deal with the reference as an ordinary reference. Outside the class package, the reference is generally treated as an opaque value that may only be accessed through the class's methods. You can access the values within a package directly, but it's not a good idea to do so because such access defeats the whole purpose of object orientation.

It's possible to bless a reference object more than once. However, the caveat to such a task is that the new class must get rid of the object at the previously blessed reference. For C and Pascal programmers, this is like assigning a pointer to malloc-ed memory and then assigning the same pointer to another location without freeing the previous location. In effect, a Perl object must belong to one and only one class at a time.

So what's the real difference between an object and a reference? Perl objects are blessed to belong to a class. References are not blessed; if they are, they belong to a class and are objects. Objects know to which class they belong. References do not have a class, as such, to which they belong.

Instance Variables

The arguments to a new() function for a constructor are called instance variables. Instance variables are used for initializing each instance of an object as it is created. For example, the new() function could expect a name for each new instance of an object created. Using instance variables allows the customization of each object as it is created.

Either an anonymous array or an anonymous hash can be used to hold instance variables. To use a hash to store the parameters coming in, you would use code similar to what is shown in Listing 5.7.


Listing 5.7. Using instance variables.
1 sub new {
2         my $type = shift;
3         my %parm = @_;
4         my $this = {};
5         $this->{'Name'} = $parm{'Name'};
6         $this->{'x'}  = $parm{'x'};
7         $this->{'y'}  = $parm{'y'};
8         bless $this, $type;
9 }

You can also use an array instead of a hash to store the instance variables. See Listing 5.8 for an example.


Listing 5.8. Using hashes for instance variables.
1 sub new {
2         my $type = shift;
3         my %parm = @_;
4         my $this = [];
5         $this->[0] = $parm{'Name'};
6         $this->[1] = $parm{'x'};
7         $this->[2] = $parm{'y'};
8         bless $this, $type;
9 }

To construct an object, you can pass the parameters with the new() function call. For example, the call to create the Cocoa object becomes this:

$mug = Cocoa::new( 'Name' => 'top',
  'x' => 10,
  'y' => 20 );

The => operator is just like the comma operator, although it's a bit more readable. You can write this code with commas instead of the => operator if that's what you prefer.

To access the variables as you would any other data members, you can use the following statements:

print "Name=$mug->{'Name'}\n";
print "x=$mug->{'x'}\n";

print "y=$mug->{'y'}\n";

Methods

A method in a Perl class is simply a Perl subroutine. Perl doesn't provide any special syntax for method definition. A method expects its first argument to be the object or package on which it is being invoked. Perl has just two types of methods: static and virtual.

A static method expects a class name as the first argument. A virtual method expects a reference to an object as the first argument. Therefore, the way each method handles the first argument determines whether the method is static or virtual.

A static method applies functionality to the class as a whole because it uses the name of the class. Therefore, functionality in static methods is applicable to all objects of the class. Generally, static methods ignore the first argument because they already know which class they are in. Therefore, constructors are static methods.

A virtual method expects a reference to an object as its first argument. Typically the first thing a virtual method does is to shift the first argument to a self or this variable; it then uses that shifted value as an ordinary reference. For example, consider the code in Listing 5.9.


Listing 5.9. Listing data items in a class.
1 sub nameLister {
2     my $this = shift;
3     my ($keys ,$value );
4     while (($key, $value) = each (%$this)) {
5         print "\t$key is $value.\n";
6     }
7 }

Line 2 in this listing is where the $this variable is set to point to the object. In line 4, the $this array is dereferenced at every $key location.

Exporting Methods with Exporter.pm

If you tried to invoke the Cocoa.pm package right now, you would get an error message from Perl at compile time about the methods not being found. This is because the Cocoa.pm methods have not been exported. To export these functions, you need the Exporter module. This is done by adding the following lines to the start of code in the package:

require Exporter;
@ISA = qw(Exporter);

These two lines force the inclusion of the Exporter.pm module and then set the @ISA array with the name of the Exporter class for which to look.

To export your own class methods, you would have to list them in the @EXPORT array. For example, to export the closeMain and declareMain methods, you would use the following statement:

@EXPORT(declareMain, closeMain);

Inheritance in a Perl class is accomplished via the @ISA array. The @ISA array does not have to be defined in every package; however, when it is defined, Perl treats it as a special array of directory names. This is akin to the @Inc array where directories are searched for files to include. In the case of the @ISA array, the paths define the classes (packages) and where to look for other class packages, if a method is not found in the current package. Thus, the @ISA array contains the names of the base classes from which the current class inherits. The search is done in the order in which the classes are listed in the @ISA arrays.

All methods called by a class do have to belong to the same class or to the base classes defined in the @ISA array. If a method isn't found in @ISA array, Perl looks for an AUTOLOAD() routine. This routine is defined as sub in the current package and is optional. To use the AUTOLOAD function, you have to use the autoload.pm package with the use Autoload; statement. The AUTOLOAD function tries to load the called function from the installed Perl libraries. If the AUTOLOAD call also fails, Perl makes one final try at the UNIVERSAL class, which is the catch-all for all methods not defined elsewhere. Perl will generate an error about unresolved functions if this step also fails.

Here are some simple rules when exporting methods. First, export only those functions that you have to. Do not export every function in your module because you will be increasing the likelihood of conflicts with a program that is using your module. Use the @EXPORT_OK array instead of the @EXPORT array if you feel that the names of your methods may clash with those in an application. Choosing long, descriptive names for functions may help eliminate problems with synonymous variable names.

Second, if you are going to have multiple versions of your module, consider setting a variable called $VERSION in your module to a numeric string; for example, "2.11" or something. This version number will be exported for you automatically and can be used with the require statement. Remember to use two digits for all integers in the version numbers because "1.10" is interpreted lower than "1.9" but higher than "1.09". You will see some modules or programs with a statement of the following form:

require 5.001;

The statement above indicates that Perl version 5.001 or greater is required. The same analogy can be used for your module with a call to a function called require_version of the following form:

$moduleName->require_version($value);

A returned value of true will indicate that it's okay to proceed. A returned value of false will indicate that the version number of the module is less than what is specified in the $value.

Invoking Methods

There are two ways to invoke a method for an object: one via a reference to an object (virtual) and the other via explicitly referring to the class name (static). A method has to be exported for you to be able to call it. Let's add a few more methods to the Cocoa class to get the file to look like the one shown in Listing 5.10.


Listing 5.10. Adding methods to the Cocoa class.
 1 package Cocoa;
 2 require Exporter;
 3
 4 @ISA = qw(Exporter);
 5 @EXPORT = qw(setImports, declareMain, closeMain);
 6
 7 #
 8 # This routine creates the references for imports in Java functions
 9 #
10 sub setImports{
11     my $class = shift @_;
12     my @names = @_;
13
14     foreach (@names) {
15      print "import " .  $_ . ";\n";
16      }
17     }
18
19 #
20 # This routine declares the main function in a Java script
21 #
22 sub declareMain{
23     my $class = shift @_;
24     my ( $name, $extends, $implements) = @_;
25
26      print "\n public class $name";
27      if ($extends) {
28           print " extends " . $extends;
29      }
30      if ($implements) {
31           print " implements " . $implements;
32      }
33    print " { \n";
34 }
35
36 #
37 # This routine declares the main function in a Java script
38 #
39 sub closeMain{
40    print "} \n";
41 }
42
43 #
44 #  This subroutine creates the header for the file.
45 #
46 sub new {
47     my $this = {};
48     print "\n /* \n ** Created by Cocoa.pm \n ** Use at own risk \n */ \n";
49     bless $this;
50     return $this;
51     }
52
53 1;

Now let's write a simple Perl script to use the methods for this class. Because you can only start and close the header, let's see how the code for a script to create a skeleton Java applet source looks. (See Listing 5.11.)


Listing 5.11. Using the methods just added in Listing 5.10.
1 #!/usr/bin/perl
2
3 use Cocoa;
4
5 $cup = new Cocoa;
6
7 $cup->setImports( 'java.io.InputStream', 'java.net.*');
8 $cup->declareMain( "Msg" , "java.applet.Applet", "Runnable");
9 $cup->closeMain();

What we are doing in this script is generating code for a Java applet called Msg, which extends the java.applet.Applet applet and implements functions that can be run. The function is called with a function $cup->... call. Lines 7 through 9 could be rewritten as functions, like this:

Cocoa::setImports($cup, 'java.io.InputStream', 'java.net.*');
Cocoa::declareMain($cup, "Msg" , "java.applet.Applet", "Runnable");
Cocoa::closeMain($cup);

This type of equivalence was shown in a previous section, "Blessing a Constructor." In both cases, the first parameter is the reference to the object itself. Running this test script generates the following output:

/*
** Created by Cocoa.pm
** Use at own risk
*/
import java.io.InputStream;
import java.net.*;

public class Msg extends java.applet.Applet implements Runnable {
}

There are a couple of points to note when calling the methods. If you have any arguments to a method, use parentheses if you are using the method -> (also known as indirect). The parentheses are required to include all the arguments with this statement:

$cup->setImports( 'java.io.InputStream', 'java.net.*');

However, this statement:

Cocoa::setImports($cup, 'java.io.InputStream', 'java.net.*');

can also be rewritten without parentheses.

Cocoa::setImports $cup, 'java.io.InputStream', 'java.net.*' ;

The choice is really yours as to how you intend to make your code readable for other programmers. Use parentheses if you feel that the code will be more readable.

Overrides

There are times when you'll want to specify which class' method to use, such as when the same-named method is specified in two different classes. For example, if the function grind is defined in both Espresso and Qava classes, you can specify which class' function to use with the use of the :: operator. These two calls:

$mess = Espresso::grind("whole","lotta","bags");
Espresso::grind($mess, "whole","lotta","bags");

use the call in Espresso, whereas the following calls use the grind() function in the Qava class:

$mess = Qava::grind("whole","lotta","bags");
Qava::grind($mess, "whole","lotta","bags");

Sometimes you want to call a method based on some action that the program you are writing has already taken. In other words, you want to use the Qava method for a certain condition and the Espresso method for another. In this case, you can use symbolic references to make the call to the required function. This is illustrated in the following example:

$method = $local ? "Qava::" : "Espresso::";
$cup->{$method}grind(@args);

Destructors

Perl tracks the number of links to objects. When the last reference to an object goes away, the object is automatically destroyed. This destruction of an object may occur after your code stops and the script is about to exit. For global variables, the destruction happens after the last line in your code executes.

If you want to capture control just before the object is freed, you can define a DESTROY() method in your class. Note the use of all capitals in the name. The DESTROY() method is called just before the object is released, allowing you to do any cleanup. The DESTROY() function does not call other DESTROY() functions. Perl doesn't do nested destruction for you. If your constructor reblessed a reference from one of your base classes, your DESTROY() may need to call DESTROY() for any base classes. All object references that are contained in a given object are freed and destroyed automatically when the current object is freed.

Normally, you don't have to define a DESTROY function. However, when you do need it, its form is as follows:

sub DESTROY {
#
# Add code here.
#
}

For most purposes, Perl uses a simple reference-based garbage collection system. The number of references to any given object at the time of garbage collection has to be greater than zero, or else the memory for that object is freed. When your program exits, an exhaustive search-and-destroy function in Perl does the garbage collection. Everything in the process is summarily deleted. In UNIX or UNIX-like systems, this may seem like a waste, but it is actually quite necessary in embedded systems or in a multithreaded environment.

Inheritance

Methods in classes are inherited with the use of the paths in the @ISA array. Variables have to be inherited and set up explicitly for inheritance. Let's say you define a new class called Bean.pm to include some of the functionality that another class, Coffee.pm, will inherit.

The example in this section demonstrates how to inherit instance variables from one class (also referred to as a superclass or base class). The steps in inheritance require calling the superclass's constructor and adding one's own instance variables to the new object.

In this example, the Coffee class is the class that inherits values from the base class Bean. The two files are called Coffee.pm and Bean.pm, respectively. The code for Bean.pm is shown in Listing 5.12.


Listing 5.12. The Bean.pm file.
 1 package Bean;
 2 require Exporter;
 3
 4 @ISA = qw(Exporter);
 5 @EXPORT = qw(setBeanType);
 6
 7 sub new {
 8     my $type = shift;
 9     my $this = {};
10     $this->{'Bean'} = 'Colombian';
11     bless $this, $type;
12     return $this;
13     }
14
15 #
16 # This subroutine sets the
17 sub setBeanType{
18     my ($class, $name) =  @_;
19     $class->{'Bean'} = $name;
20     print "Set bean to $name \n";
21     }
22 1;

In this listing, the $this variable sets a value in the anonymous hash for the 'Bean' class to be 'Colombian'. The setBeanType function method is also declared so that the item referred to by the word 'Bean' is set for any class that is sent in as an argument. Therefore, you can use this setBeanType function in other classes to set the value of any member whose name is 'Bean'.

The subroutine for resetting the value of 'Bean' uses the $class reference to get to the anonymous hash for the object. Remember that it is a reference to this anonymous hash that created the reference in the first place with the new() function.

The values in the Bean class are inherited by the Coffee class. The Coffee.pm file is shown in Listing 5.13.


Listing 5.13. Using inheritance.
 1    #
 2    # The Coffee.pm file to illustrate inheritance.
 3    #
 4    package Coffee;
 5    require Exporter;
 6    require Bean;
 7    @ISA = qw(Exporter, Bean);
 8    @EXPORT = qw(setImports, declareMain, closeMain);
 9     #
10     # set item
11     #
12     sub setCoffeeType{
13         my ($class,$name) =  @_;
14         $class->{'Coffee'} = $name;
15         print "Set coffee type to $name \n";
16         }
17     #
18     #  constructor
19     #
20     sub new {
21         my $type  = shift;
22         my $this  = Bean->new();     ##### <-- LOOK HERE!!! ####
23         $this->{'Coffee'} = 'Instant';  # unless told otherwise
24         bless $this, $type;
25         return $this;
26         }
27     1;

Note the use of the require Bean; statement at line 6. (See Chapter 4, "Introduction to Perl Modules," in the section titled "Using Perl Modules" for the reasons why the require statement is used instead of the use statement.) This line forces the inclusion of the Bean.pm file and all its related functions without importing the functions until compile time. Lines 12 through 16 define a subroutine to use when resetting the value of the local variable in $class->{'Coffee'}.

Look at the new() constructor for the Coffee class. The $this reference points to the anonymous hash returned by Bean.pm, not a hash created locally. In other words, the following statement creates an entirely different hash that has nothing to do with the hash created in the Bean.pm constructor.

my $this = {}; # This is not the way to do it for inheritance.
my $this = $theSuperClass->new(); # this is the way.

Listing 5.14 illustrates how to call these functions.


Listing 5.14. Using inheritance.
 1 #!/usr/bin/perl
 2 push (@Inc,'pwd');
 3 use Coffee;
 4 $cup = new Coffee;
 5 print "\n -------------------- Initial values ------------ \n";
 6 print "Coffee: $cup->{'Coffee'} \n";
 7 print "Bean: $cup->{'Bean'} \n";
 8 print "\n -------------------- Change Bean Type ---------- \n";
 9 $cup->setBeanType('Mixed');
10 print "Bean Type is now $cup->{'Bean'} \n";
11 print "\n ------------------ Change Coffee Type ---------- \n";
12 $cup->setCoffeeType('Instant');
13 print "Type of coffee: $cup->{'Coffee'} \n";

The initial values for the 'Bean' and 'Coffee' indexes in the anonymous hash for the object are printed first. The member functions are called to set the values to different names and are printed out.

Here is the output of the script:

-------------------- Initial values ------------
Coffee: Instant
Bean: Colombian

-------------------- Change Bean Type ----------
Set bean to Mixed
Bean Type is now Mixed

------------------ Change Coffee Type ----------
Set coffee type to Instant
Type of coffee: Instant

Methods can have several types of arguments. It's how you process the arguments that counts. For example, you can add the method shown in Listing 5.15 to the Coffee.pm module.


Listing 5.15. Variable-length lists of parameters.
1 sub makeCup {
2      my ($class, $cream, $sugar, $dope) = @_;
3      print "\n================================== \n";
4      print "Making a cup \n";
5      print "Add cream \n" if ($cream);
6      print "Add $sugar sugar cubes\n" if ($sugar);
7      print "Making some really addictive coffee ;-) \n" if ($dope);
8      print "================================== \n";
9 }

This function takes three arguments, but it processes them only if it sees them. To test this functionality, consider the Perl code shown in Listing 5.16.


Listing 5.16. Testing variable length lists.
 1    #!/usr/bin/perl
 2    push (@Inc,'pwd');
 3    use Coffee;
 4    $cup = new Coffee;
 5    #
 6    #  With no parameters
 7    #
 8    print "\n Calling  with no parameters: \n";
 9    $cup->makeCup;
10    #
11    #  With one parameter
12    #
13    print "\n Calling  with one parameter: \n";
14    $cup->makeCup('1');
15    #
16    #  With two parameters
17    #
18    print "\n Calling  with two parameters: \n";
19    $cup->makeCup(1,'2');
20    #
21    #  With all three parameters
22    #
23    print "\n Calling  with three parameters: \n";
24    $cup->makeCup('1',3,'1');

Line 9 calls the function with no parameters. In Line 14, the call is with one parameter. The parameters are passed either as strings or integers-something this particular method does not care about (see lines 19 and 24). However, some methods you write in the future may require this distinction.

Here's the output from this program:

 Calling  with no parameters:

==================================
Making a cup
==================================

 Calling  with one parameter:

==================================
Making a cup
Add cream
==================================

 Calling  with two parameters:

==================================
Making a cup
Add cream
Add 2 sugar cubes
==================================

 Calling with three parameters:

==================================
Making a cup
Add cream
Add 3 sugar cubes
Making some really addictive coffee ;-)
==================================

In any event, you can have default values in the function to set if the expected parameter is not passed in. Thus, the behavior of the method can be different depending on the number of arguments you pass into it.

Overriding Methods

Inheriting functionality from another class is beneficial in that you can get all the exported functionality of the base class in your new class. To see an example of how this works, let's add a function called printType in the Bean.pm class. Here's the subroutine:

sub printType {
    my $class =  shift @_;
    print "The type of Bean is $class->{'Bean'} \n";
}

Do not forget to update the @EXPORT array by adding the name of the function to export. The new statement should look like this:

@EXPORT = qw(setBeanType, printType, printType);

Next, call the printType function. The following three lines show three ways to call this function:

$cup->Coffee::printType();
$cup->printType();
$cup->Bean::printType();

The output from all three lines is the same:

The type of Bean is Mixed
The type of Bean is Mixed
The type of Bean is Mixed

Why is this so? Because there is no printType() function in the inheriting class, the printType() function in the base class is used instead. Naturally, if you want your own class to have its own printType function, you would define its own printType function.

In the Coffee.pm file, you would add the following lines to the end of the file:

#
# This routine prints the type of $class->{'Coffee'}
#
sub printType {
    my $class =  shift @_;
    print "The type of Coffee is $class->{'Coffee'} \n";
}

@EXPORT would also have to be modified to work with this function:

@EXPORT = qw(setImports, declareMain, closeMain, printType);

The output from the three lines now looks like this:

The type of Coffee is Instant
The type of Coffee is Instant
The type of Bean is Mixed

Now the base class function is called only when the Bean:: override is given. In the other cases, only the inherited class function is called.

What if you do not know what the base class name is or even where the name is defined. In this case, you can use the SUPER:: pseudoclass reserved word. Using the SUPER:: override allows you to call an overridden superclass method without actually knowing where that method is defined. The SUPER:: construct is meaningful only within the class.

If you're trying to control where the method search begins and you're executing in the class itself, you can use the SUPER:: pseudoclass, which says to start looking in your base class's @ISA list without having to explicitly name it.

$this->SUPER::function( ... argument list ... );

Therefore, instead of Bean::, you can use SUPER::. The call to the function printType() becomes this:

$cup->SUPER::printType();

Here's the output:

The type of Bean is Mixed

A Few Comments About Classes and Objects in Perl

One advertised strength of object-oriented languages is the ease with which new code can use old code. Packages and modules in Perl provide a great deal of data encapsulation. You are never really guaranteeing that a class inheriting your code will not attempt to access your class variables directly. They can if they really want to. However, this type of procedure is considered bad practice, and shame on you if you do it.

When writing a package, you should ensure that everything a method needs is available via the object or is passed as a parameter to the method. From within the package, access any global variables only through references passed in via methods.

For static or global data to be used by the methods, you have to define the context of the data in the base class using the local() construct. The subclass will then call the base class to get the data for it. On occasion, a subclass may want to override that data and replace it with new data. When this happens, the superclass may not know how to find the new copy of the data. In such cases, it's best to define a reference to the data and then have all base classes and subclasses modify the variable via that reference.

Finally, you'll see references to objects and classes like this:

use Coffee::Bean;

This code is interpreted to mean "look for Bean.pm in the Coffee subdirectory in all the directories in the @Inc array." So, if you were to move Bean.pm into the ./Coffee directory, all the previous examples would work with the new use statement. The advantage to this approach is that you have one file for the parent class in one directory and the files for each base class in their own sub-directories. It helps keep code organized. Therefore, to have a statement like this:

use Another::Sub::Menu;

you would see a directory subtree like this:

./Another/Sub/Menu.pm

Let's look at an example of a simple portfolio manager class called Invest.pm. There are two subclasses derived from it that manage the type of funds. The three files are shown in Listings 5.17, 5.18, and 5.19. The test code to use these modules is shown in Listing 5.20. The Invest.pm file is placed in the current directory, and the Stock.pm and Fund.pm files are placed in the Invest subdirectory.


Listing 5.17. The ./Invest.pm file.
 1 package Invest;
 2
 3 require Exporter;
 4 @ISA = (Exporter);
 5
 6 =head1 NAME
 7
 8 Letter - Sample module to simulate Bond behaviour
 9
10 =head1 SYNOPSIS
11
12     use Invest;
13     use Invest::Fund;
14     use Invest::Stock;
15
16     $port = new Invest::new();
17
18      $i1 = Invest::Fund('symbol' => 'twcux');
19      $i2 = Invest::Stock('symbol' => 'INTC');
20      $i3 = Invest::Stock('symbol' => 'MSFT');
21
22      $port->Invest::AddItem($i1);
23      $port->Invest::AddItem($i2);
24      $port->Invest::AddItem($i3);
25
26      $port->ShowPortfolio();
27
28 =head1 DESCRIPTION
29
30 This module provides a short example of generating a letter for a
31 friendly neighborbood loan shark.
32
33 The code begins after the "cut" statement.
34 =cut
35
36 @EXPORT = qw( new, AddItem, ShowPortfolio, PrintMe);
37
38 @portfolio = ();
39 $portIndex = 0;
40
41 sub Invest::new {
42         my $this = shift;
43         my $class = ref($this) || $this;
44         my $self = {};
45         bless $self, $class;
46      $portIndex = 0;
47
48      printf "\n Start portfolio";
49         return $self;
50 }
51
52 sub Invest::AddItem {
53      my ($type,$stock) = @_;
54      $portfolio[$portIndex] = $stock;
55      $portIndex++;
56 }
57
58 sub Invest::ShowPortfolio  {
59      my $i;
60      printf "\n Our Portfolio is:";
61      foreach $i (@portfolio) {
62           print "\n ".  $i->{'shares'} . " shares of " . $i->{'symbol'};
63      }
64      print "\n";
65 }
66
67 sub PrintMe {
68      my $this = shift;
69      print "\n Class : $$this";
70 }
71 1;


Listing 5.18. The ./Invest/Stock.pm file.
 1 package Invest::Stock;
 2
 3 require Exporter;
 4 @ISA = (Exporter);
 5 @EXPORT = qw( new );
 6
 7 sub new {
 8         my $this = shift;
 9         my $class = ref($this) || $this;
10         my $self = {};
11      my %parm = @_;
12
13         bless $self, $class;
14      $self->{'symbol'} = $parm{'symbol'};
15      $self->{'shares'} = $parm{'shares'};
16      printf "\n New stock $parm{'symbol'} added";
17         return $self;
18 }
19
20 1;


Listing 5.19. The ./Invest/Fund.pm file.
 1 package Invest::Fund;
 2
 3 require Exporter;
 4 @ISA = (Exporter,Invest);
 5
 6 @EXPORT = qw( new );
 7
 8 sub new {
 9         my $this = shift;
10         my $class = ref($this) || $this;
11         # my $self = {};
12         my $self = Invest::new();
13      my %parm = @_;
14
15         bless $self, $class;
16      $self->{'symbol'} = $parm{'symbol'};
17      $self->{'shares'} = $parm{'shares'};
18      printf "\n new mutual fund $parm{'symbol'} added";
19         return $self;
20 }
21
22 1;


Listing 5.20. Using the Invest, Fund, and Stock files.
 1 #!/usr/bin/perl
 2
 3 push(@Inc,'pwd');
 4
 5 use Invest;
 6 use Invest::Fund;
 7 use Invest::Stock;
 8
 9 $port = new Invest;
10
11 $i1 = new Invest::Fund('symbol' => 'TWCUX', 'shares' => '100');
12 $i2 = new Invest::Fund('symbol' => 'FIXLL', 'shares' => '200');
13
14 $i3 = new Invest::Stock('symbol' => 'INTC', 'shares' => '400');
15 $i4 = new Invest::Stock('symbol' => 'MSFT', 'shares' => '200');
16
17 print "\n";
18 $port->Invest::AddItem($i1);
19 $port->Invest::AddItem($i2);
20 $port->Invest::AddItem($i3);
21 $port->Invest::AddItem($i4);
22 print "\n";
23
24 $port->ShowPortfolio();

Summary

This chapter has provided a brief introduction to object-oriented programming in Perl. Perl provides the OOP features of data encapsulation and inheritance using modules and packages. A class in Perl is simply a package. This class package for a class provides all the methods for objects created for the class.

An object is simply a reference to data that knows to which class it belongs. A method in a class is simply a subroutine. The only catch with writing such methods is that the name of the class is always the first argument in the method.

The bless() function is used to tie a reference to a class name. The bless() function is called in the constructor function new() to create an object and then connect the reference to the object with the name of the class.

In inheritance, the base class is the class from which methods (and data) are inherited. The base class is also called the superclass. The class that inherits these items from the superclass is called the subclass. Multiple inheritance is allowed in Perl. Data inheritance is the programmers' responsibility with the use of references. The subclass is allowed to know things about its immediate superclass, and the superclass is allowed to know nothing about a subclass. Subclasses exist as .pm files in subdirectories under the superclass directory name.


Previous chapter Chapter contents Contents Next chapter




With any suggestions or questions please feel free to contact us