Perl Page #2: Object-Oriented Programming in Perl Made Easy


There's strictly no warranty for the correctness of this text. You use any of the information provided here at your own risk.

Caution! This Text was written by a hobby-programmer for hobby-programmers, who want fast results in their Perl-scripts.
The terminology of object-oriented programming may not have been used accurately enough for the needs of professional programmers or students of computer science, for example.


Contents:

  1. Introduction
  2. Classes as Definitions of Containers
  3. Instantiation of an Object
  4. A first Example-Script
  5. Passing of Arguments; Extension of the Example
  6. Virtual Depiction of Real Life Items
  7. Processing of Attributes
  8. Hash-Syntax for Passing Arguments at Object-Instantiation
  9. What "$self->{attribute}" really is
  10. Arrays and Hashes as Attributes in a Class - Using "push()" and foreach-Loops
  11. Passing Objects to Functions
  12. Inheritance
  13. OOP-Syntax in Perl/Tk
  14. Interaction of Objects and Class-Design
  15. A Larger Example-Program
  16. Further Reading


1. Introduction

Many programmers still seem to have difficulties using object-oriented programming (OOP) in Perl (Perl 5).

It's not that difficult though, if you know the purpose of all this, and when you concentrate on that purpose instead of getting lost in the details of the implementation.

The following text deals with Perl 5's built-in OOP. Some people use the module "Moose" instead, but this text is not about that.

Many programming-problems can be solved just with the following text. However, not all questions of OOP are covered, and maybe the terminology of object-oriented programming is not always used as accurately as it would be required for students of computer science, for example.
Further reading would include "perldoc perlboot", "perldoc perltoot" or "perldoc perlootut".
There's also the book "Intermediate Perl" or its previous version "Learning Perl Objects, References, and Modules" (chapter 8 of this book seems to have been copied into "perldoc perlboot").
There's the even more detailed book "Object Oriented Perl".


2. Classes as Definitions of Containers

A class could be seen as a definition of a container, that holds variables and functions.
An Array, for example, is a container too, it can hold one or more variables (or in Perl 5 even other arrays or hashes).

Furthermore, a class can hold functions.

The variables inside a class are called "attributes", the functions inside a class are called "methods".
The main advantage is, that all attributes of the class are known inside the methods. They don't have to be passed to the methods as arguments.


3. Instantiation of an Object

Usually several arrays or hashes are used in a script.
Similarly, there can be several of these containers, whose contents are defined by a class. First, a class is defined, and then, there's an instruction in the script, that one of these containers, which are defined by the class, should be used now.
After that instruction, the container is known inside the script under a given name. This kind of container is called "object", and, as mentioned above, several of these objects can be requested in a script. This process is called "instantiation": An object, that is an instance of the class, is requested.


4. A first Example-Script

So, how does a Perl-Script look like, in which a class is defined and in which an object of this class is instantiated afterwards? Here's an example:

#!/usr/bin/perl

# OOP-example-script 1

use warnings;
use strict;

package Lamp;

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state};
        print "\n";
    }

package main;

my $lamp = Lamp->new();
$lamp->showState();

---

Please run the script once and notice (maybe with amazement), that it runs without complaints.

How does it work? Step by step. The line:

package Lamp;

is - for our purposes - the beginning of a class-definition, where the name of the class is "Lamp".
class-names usually start with a capital letter.
While writing the text of the methods inside a class, there should be indentations as shown, otherwise the script gets rather messy.

Every class has a method "new()". This method is called, when an object is instantiated.
We don't have to bother with the strange code in the method "new()" (I call it "class-magic"). In the end it means, when we call this method at object-instantiation, we get an object of the kind we want.
An exception is the line

my $self = {state => "off"};

Inside the curly brackets, the attributes of the class are defined. Here's an attribute "state" with the value "off", for example.

After the method "new()", other methods of the class follow.
Here's a method "showState()".
Each method inside a class has "$self" as its first parameter. This is either expressed by this line at the beginning of the method:

my $self = shift;

Or, if the method has several parameters, by a line like:

my ($self, $second_parameter, $third_parameter) = @_;

With this "$self" as the first parameter, the method is declared as a part of the class, and that way, all the attributes of the classed are passed to the method.
Although the "shift()" in the line above means, that an argument is passed to the method, it is not the programmer, that passes this argument by calling the method. Instead, in this special case the argument "$self" is passed by an automatic mechanism.

After that, the method can process the attributes. As shown, the attributes can be accessed with the syntax:

$self->{state}

After the definition of the class is finished, either another class can be defined or the line

package main;

takes the script back into its main namespace.
With the line

my $lamp = Lamp->new();

an object of the class "Lamp" is instantiated. The method "new()" inside the class is called. After that, an object of the class "Lamp" can be accessed using the variable "$lamp".

With the line

$lamp->showState();

the method "showState()" inside the $lamp-object is called. The code, that runs then, depends on the definition of this method inside the class "Lamp". So in this script, the value of the attribute "state" is printed.

By now, the following has been achieved:

Well, the syntax above is used in many Perl-books and -documents such as "perldoc perlobj".
But actually, a package can also be written inside curly brackets. That makes the code look much more like in other object-orientated languages such as C++ or Java. I'm gonna use the other syntax from now on:

#!/usr/bin/perl

# OOP-example-script 1

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state};
        print "\n";
    }
}

my $lamp = Lamp->new();
$lamp->showState();

5. Passing of Arguments; Extension of the Example

Arguments can be passed to methods in classes, just like they would be passed to other (ordinary) functions.
This works also for the method "new()". By passing arguments to this method, objects with different attributes can be instantiated.

Let's extend the "Lamp"-example a bit (similar to Weigend, Python ge-packt, second edition, 2005, S. 294 f.):

#!/usr/bin/perl

# OOP-example-script 2

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {name => shift,
                    lightintensity => shift,
                    state => "off"};
        return bless($self, $classname);
    }

    sub switchOn {
        my $self = shift;
        $self->{state} = "on";
        print "'" . $self->{name} . "' is on at " . $self->{lightintensity} . " Watt.\n";
    }

    sub switchOff {
        my $self = shift;
        $self->{state} = 0;
        print "'" . $self->{name} . "' is off.\n";
    }

    sub newLightBulb {
        my ($self, $light) = @_;
        if ($self->{state} eq "on") {
            print "Light bulb can not be changed. ";
            print "First, '" . $self->{name} . "' has to be switched off.\n";
        } else {
            $self->{lightintensity} = $light;
            print "Light bulb in '" . $self->{name} . "' has been changed. ";
            print "The new bulb has $light Watt.\n";
            $self->switchOn();
        }
    }
}

my $lamp1 = Lamp->new("First Lamp", 50);
my $lamp2 = Lamp->new("Second Lamp", 40);
$lamp1->switchOn();
$lamp2->switchOn();
$lamp2->newLightBulb(100);
$lamp2->switchOff();
$lamp2->newLightBulb(100);

---

There are two objects "$lamp1" und "$lamp2" here, which are both defined by the class "Lamp". While "$lamp1" has constantly 50 Watt, "$lamp2" is at first instantiated with the attribute "40 Watt", then the light intensity of "$lamp2" changes again by calling the method "newLightBulb()".

When the method "newLightBulb()" is called, an argument is passed to it, assigning a value to "$light" using the function "shift()" (which refers to the delivery-array "@_", if the function is called without arguments). "$light" is an ordinary variable here, which is only known inside this method, but not in the other methods of the class.

Inside methods, attributes can not only be read, but also be defined new, as shown for example in the line:

$self->{state} = "on";

Furthermore, not only the attributes are known to the methods, but also the other methods of the class. For example, after the bulb has been changed, the method "newLightBulb()" calls the method "switchOn()" with the line

$self->switchOn();

The method itself has called the other method, it wasn't called from outside.

Besides, methods can have return values, which are passed back with the keyword "return", just like other functions do.


6. Virtual Depiction of Real Life Items

The example-script above with the lamp ("OOP-example-script 2") shows, that objects, while holding variables and functions, are capable of depicting real life items inside a computer-program. That's especially, what makes object-oriented programming interesting for many people. In the example, certain properties of a real lamp, like its state (switched on/switched off), the light intensitiy of the bulb or the changing of the bulb are virtually represented.

The chapters above already show the basics of Perl's OOP. The rest of the text mainly covers certain details.


7. Processing of Attributes

As mentioned above, methods can access an attribute with the syntax

$self->{attribute}

They can for example read its value or redefine it.
It's also possible to define the attribute inside the method for the first time. So this doesn't have to happen necessarily in "new()".

Attributes can also be defined in the method "new()" like this:

...
    sub new {
        my $classname = shift;
        my $self = {one  => 1};
        $self->{two} = 2;
        $self->{three} = 3;
        return bless($self, $classname);
    }
...

8. Hash-Syntax for Passing Arguments at Object-Instantiation

A popular feature in OOP-scripts in Perl is to be able to pass pairs of values in hash-syntax ("key => value") to the method "new()" at object-instantiation and that default-values are defined in the class, if no values are passed.
This is achieved by extending the "class-magic" in the method "new()" as shown in the following example:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-example-script 3

package Lamp { 
 
    sub new { 
        my $classname = shift;
        my $args = {@_}; 
        my $self = { lightintensity => $args->{lightintensity} || 40,
                     name     => $args->{name} || "Default lamp" }; 
        return bless($self, $classname); 
    }           

    sub showLightIntensity {
        my $self = shift;
        print "'" . $self->{name} . "' has a light bulb with ";
        print $self->{lightintensity} . " Watt.\n";
    }
}

my $lamp = Lamp->new(lightintensity => 11);
$lamp->showLightIntensity();

Note: One thing to be kept in mind when using this construction is, that the expression

this || that

evaluates to "that", if "this" is 0. So when you pass a light intensity of 0 to "->new()", the function will set the light intensity to the default value (which is 40 here). This may not be, what you wanted, and you have to take care of that yourself, if the problem is relevant to your program. Example:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-example-script 3 (warning)

package Lamp { 
 
    sub new { 
        my $classname = shift;
        my $args = {@_}; 
        if ($args->{lightintensity} == 0) {
            print "\nWarning: Did you want 'lightintensity' == 0?\n\n";
        }
        my $self = { lightintensity => $args->{lightintensity} || 40,
                     name     => $args->{name} || "Default lamp" }; 
        return bless($self, $classname); 
    }           

    sub showLightIntensity {
        my $self = shift;
        print "'" . $self->{name} . "' has a light bulb with ";
        print $self->{lightintensity} . " Watt.\n";
    }
}

my $lamp = Lamp->new(lightintensity => 0);
$lamp->showLightIntensity();


9. What "$self->{attribute}" really is

As we've seen, attributes are stored in something, that is defined in the method "new()" with this line:

my $self = {};

So what is that? In Perl, you can store the data of an array or a hash not only in a common named array or hash, like this:

my @a = (10, 22);
my %h = (a => 10, b => 20);

But to get a similar result, you can also store that data in an anonymous array or hash, and then point a reference to it:

my $aref = [10, 22];
my $href = {a => 10, b => 20};

As you can see, an anonymous array is indicated by square brackets [...].
And an anonymous hash is indicated by curly brackets {...}.

The data can then be accessed as shown here:

#!/usr/bin/perl

use warnings;
use strict;

# Ordinary array:
my @a = (10, 20);
print $a[1] . "\n";

# Anonymous array with array reference pointing to it:
my $aref = [10, 20];
print $aref->[1] . "\n";

# Ordinary hash:
my %h = (a => 10, b => 20);
print $h{a} . "\n";

# Anonymous hash with hash reference pointing to it:
my $href = {a => 10, b => 20};
print $href->{a} . "\n";

# By the way: Number of elements minus 1:
print $#a . "\n";
print $#{$aref} . "\n";

So, the "$self" in classes is just that: A hash reference, pointing to an anonymous hash.
And the data inside that anonymous hash can be accessed through the reference.


10. Arrays and Hashes as Attributes in a Class - Using "push()" and foreach-Loops

Like in ordinary hashes, inside an anonymous hash not only scalar variables, but also other anonymous arrays and hashes can be stored. That's why you can also use that kind of data as attributes in a class.

Here's an example-script, how this can be done:

#!/usr/bin/perl

# OOP-example-script 4

use warnings;
use strict;

package Bag {

    sub new {
        my $classname = shift;
        my $self = {};
        $self->{fruits} = ["Apple", "Banana"];
        $self->{books} = {crime => "Christie",
                          cooking => "Oliver"};
        return bless($self, $classname);
    }

    sub addFruit {
        my ($self, $fruit) = @_;
        push( @{$self->{fruits}}, $fruit );
    }

    sub addBook {
        my ($self, $category, $author) = @_;
        $self->{books}{$category} = $author;
        print $category . "\t" . $self->{books}{$category} . "\n";
    }

    sub showFruits {
        my $self = shift;
        foreach my $i ( @{$self->{fruits}} ) {
            print "$i\n";
        }
        print "\n";
    }

    sub showBooks {
        my $self = shift;
        print $self->{books}->{crime} . "\n";
        print $self->{books}->{cooking} . "\n\n";
        # Or:
        my $i;
        foreach $i (keys( %{$self->{books}} )) {
            print "$i\t" . $self->{books}{$i} . "\n";
        }
    }
}

my $bag = Bag->new();
$bag->addFruit("Peach"); 
$bag->addBook("psychology", "Freud");
print "\n";
$bag->showFruits();
$bag->showBooks();

---

The elements of (anonymous) arrays and hashes, that are attributes in a class, can be accessed using the following syntax:

print $self->{fruits}[0] . "\n"; # Array-element

print $self->{books}{crime} . "\n"; # Hash-element

Actually, this is short for "$self->{fruits}->[0]" and "$self->{books}->{crime}".

And here's the explanation, what that is: As seen above, "$self" is a reference to an anonymous hash. This hash has a key called "fruits", and the corresponding value is an anonymous array. So "$self->{fruits}" is again a reference: A reference to the anonymous array inside the anonymous hash.
If this reference was called "$aref" instead of "$self->{fruits}", you'd have the same expression as in the chapter above, when accessing an element of the anonymous array:

$aref->[0]

But because the reference is called "$self->{fruits}", you have this expression, when accessing an element of the anonymous array:

$self->{fruits}->[0]

The same goes for hashes: If the hash reference was called "$href", you'd have "$href->{crime}", but as the reference is called "$self->{books}", it is "$self->{books}->{crime}".


Using "push()" on Arrays, that are Attributes of a Class

As explained above, an array, that is an attribute of a class, is managed by a reference (called for example "$self->{fruits}").
If you want to append elements to an array, you usually use the function "push()". In earlier versions of Perl, you could also use a reference to an array to push to it. But that's no longer possible, it would result in this error:

Experimental push on scalar is now forbidden

Instead, you have to dereference the reference, and use "push() with the resulting array. That is possible, because the result of dereferencing using "@{ ... }" is not just any array, but it is exactly the array, the reference points to. It has the same memory address. That's why elements are added to just that array, when using "push()":

push( @{ $self->{fruits} }, $fruit );

Just as shown in the method "addFruit()" above.


Using foreach-Loops with Arrays, that are Attributes of a Class

Setting up a foreach-loop works in a similar way: You can't use references directly, because the foreach-loop only works with ordinary arrays. So the array reference called "$self->{fruits}" has to be dereferenced, before the single array elements can be accessed in the loop. The method "showFruits()" demonstrates, how this is done:
The term "@{ ... }" is put around the reference. The resulting array can be used in the foreach-loop. It is exactly the array, the reference points to, it has the same memory address.
So a foreach-loop using an array, that is an attribute of a class, looks like this:

my $i;
foreach $i ( @{$self->{fruits}} ) {
    ...;
}

In a similar way the hash-reference "$self->{books}" can be dereferenced, as shown in the method "showBooks().

I have to admit, all of that isn't ideal, but I promise, that's as ugly and complicated as OOP in Perl gets. So if you've managed to understand this chapter, you're almost there.


11. Passing Objects to Functions

Objects can be passed to functions easily without any problems.

That's a big advantage to other data-structures in Perl. You just pass the variable that is labeled with the name of the object to the function, and at once the object with all its attributes and methods can be accessed inside the function. Without having to dereference anything. If the data in your program is more complex, that's much clearer than passing references to larger "array of array" oder "hash of hash"-structures. Here's an example for passing an object:

#!/usr/bin/perl

# OOP-example-script 5

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state}. "\n";
    }
}

sub showLampState {
    my $l = shift;
    $l->showState();
}

my $lamp = Lamp->new();
showLampState($lamp);

---

In the same way, objects can also be passed to methods of other objects.


12. Inheritance

Suppose, you'd like to write a class, that's very similar to another class, that you've already written. Most attributes and methods are identical, the new class just differs in two or three methods. Then it would be useful, if the new class could just take over the other methods and attributes from the already existing class, so that in the new class you'd just have to write the differences between the classes. In OOP, this is achieved by socalled "inheritance". Here's an example:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-example-script 6

package Animal {

    sub new {
        my $classname = shift;
        my $self  = {"name" => shift};
        return bless($self, $classname);
    }

    sub startMoving {
        my $self = shift;
        print $self->{name} . " is walking.\n";
    }

    sub makeNoise {
        my $self = shift;
        print "Woof! Woof!\n";
    }
}

package Cat {

    use base 'Animal';

    sub showName {
        my $self = shift;
        print $self->{'name'} . "\n";
    }

    sub makeNoise {
        my $self = shift;
        print "Meow!\n";
    }
}

my $cat = Cat->new("Tibby");
$cat->showName();
$cat->startMoving();
$cat->makeNoise();

---

With "use base ..." the child-class ("Cat") inherits all attributes and methods from the parent-class ("Animal"), here the attribute "name" and the methods "startMoving()" und "makeNoise()".
Obviously, at object-instantiation the argument "Tibby" is passed to the "new()"-method of "Animal" (which is a bit surprising to me).
The call "$cat->showName();" shows, that the "Cat"-object has inherited the attribute "name".
The method "makeNoise()" was inherited from "Animal", but was overwritten in "Cat" with another content. The call "$cat->makeNoise();" refers therefore to the overwritten method in the "Cat"-object.

Besides "use base ..." there's also "use parent ...". Furthermore there's an older construction of inheritance using the "@ISA"-array. If the syntax above isn't enough, further reading would be required. I haven't used inheritance that often yet.


13. OOP-Syntax in Perl/Tk

If you've become familiar with the above basics of Perl's traditional OOP, you'll also understand the syntax better, that is used in Perl-modules, written in OOP. Especially the commands, that are used in Perl/Tk to create graphical user-interfaces can be understood better.
A typical, minimal Perl/Tk-script (that creates a simple window with a tiny label "Hello") would look, for example, like this:

#!/usr/bin/perl

use warnings;
use strict;

use Tk;

my $mw = MainWindow->new();
my $lab = $mw->Label(-text => "Hello");
$lab->pack();
$mw->MainLoop();

---

The Perl/Tk-commands use OOP-syntax. You could "catch" them without Tk with your own classes, and that way kind of emulate Tk without the functionality:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-example-script 7

package MainWindow {

    sub new {
        my $classname = shift;
        my $self = {};
        print "New MainWindow would have been created.\n";
        return bless($self, $classname);
    }

    sub Label {
        my $self = shift;
        return Label->new(@_);
    }

    sub MainLoop {
        my $self = shift;
        print "Now the mainloop would run.\n";
    }
}

package Label {

    sub new {
        my $classname = shift;
        my $args = {@_}; 
        my $self = { -text => $args->{-text} || "" };
        print "New label-widget would have been created. ";
        print "Its text would be '" . $self->{-text} . "'.\n";
        return bless($self, $classname);
    }

    sub pack {
        my $self = shift;
        print "The label-widget would have been packed now.\n";
    }
}

my $mw = MainWindow->new();
my $lab = $mw->Label(-text => "Hello");
$lab->pack();
$mw->MainLoop();

---

So, if you know Perl's OOP, you'll also understand Perl/Tk (and other object-oriented modules) better.


14. Interaction of Objects and Class-Design

With OOP it is possible to write even large programs in Perl, that don't become a mess.
Typically, such a program is composed of several classes, while the main-namespace of the script just contains one or two commands: An object-instantiation and maybe a call to another method, that gets the program started.
The other objects are then instantiated inside the called class. This is called a "has a"-relation to these objects.
The art of OOP is to declare in a useful way, what the objects should represent and what the relations between the objects are. This is called "modelling" of classes, in a way it's almost like the work of a sculptor.

Classes should be enclosed in themselves. From outside, only methods should be called, but the attributes of a class shouldn't be accessed directly from outside. Objects can be understood as machines in software, that are "used" by other objects. If a person in the real world inserts a coin into a soft-drink machine, it just gets a drink. The user doesn't get in touch with the internal mechanisms of the machine. He doesn't know anything about it, and he doesn't have to know (and in this case that doesn't do any harm (- it may in other cases)). Objects should be just like that.

Objects should be enclosed in themselves, and they shouldn't know more about other object than they have to. It's not always easy, to keep that rule up. You're easily tempted to control the relations between several objects by a superior "all-knowing" object. Such a "God Object" should be avoided, if possible.

In large programs with graphical user-interfaces (GUI), it may be useful to separate the data-processing from the graphical representation of the data in several classes. This can be done with the concept called "Model View Controller (MVC)".
Ideally, with this it should be possible, to change the graphical Toolkit (like Tk, Gtk, Qt) by just changing the class "View". In real life, most programs only support one toolkit though.


15. A Larger Example-Program

Here, at the end of the text, there's one more, a bit larger example-program. It shows several animated "Ball"-Objects in a graphical Tk-window:

Regarding object-oriented programming, only constructions were used, that have been explained in the text above.
The program can be found here.


16. Further Reading

My little series about Perl continues with "Perl Page #3: Creating Window-Applications using Perl/Tk".



Email: hlubenow2 {at-symbol} gmx.net
Back