OK - here's a simple object to get us started. It's intially trivial but we can extend it as we go.
Let's write a calculator that we can use like this:
Code:
my $calc = new Calculator;
$calc->initialise( 7 );
$calc->multiply( 2 );
$calc->add( 6 );
print $calc->result(), "\n";
We can see the methods we want and we can see how the calc object only needs one attribute, it's current value. initialise() sets it and result() returns it. The other methods should be child's play.
First we need an object. According to the bok, an object is a blessed reference. Perl doesn't care what sort of reference but I almost always use hash references because they are future-proof - you can add more and more attributes as keys in the hash - and the use of named keys can lead to more self-documenting code.
We bless a reference into a class. The bless call simply takes a reference to be blessed and a string as the name of the class. Now, here's the key to object syntax: the -> operator. When you write
or
perl goes and looks in a package with the same name as $class and calls it, passing $class as the first arg, as if you had written
This means that, if we write
Code:
my $calc = new Calculator;
perl looks in package Calculator for a method called new and calls it with the scalar 'Calculator' as it's first argument. It's actually the only argument because we didn't supply any others.
In this case, we are calling a constructor. There's nothing special about the name new(), but it makes the code clearer so we'll stick with it.
So how do we write new()? It gets the name of it's class as an argument and needs to return a blessed reference so, at it's simplest, we only need
Code:
package Calculator;
sub new {
my $class = shift;
my $self = {}; # a reference.
bless( $self, $class ); # now $self is an object
return $self; # return it
}
I'm going to overcomplicate here because there's a habit which is well worth getting into. The constructor we've created is a class constructor - that is: it is called with the name of the class as it's argument. When we get frisky with objects, we will find uses for copy constuctors. In essence, these let us say
Code:
my $first_calculator = new Caculator;
my $new_calculator = $first_calculator->new();
Note that we didn't tell perl the class - it worked it out from the class of $first_calulator. The upshot is that the first argument to new() here is
an object of class Calculator rather than the string [tt]Calculator[/tt]. Fortunately we can recover the class of an object with the ref() function, so it's easy to rewrite our new method to accept either the scalar 'Calculator' or a reference to an object of type Calculator. We just need to look and see if our first argument is a reference or not. ref() returns either the class name or undef, so we can use || to say "the class of the prototype if it's a ref, or the prototype itself".
The modified new()looks like this
Code:
package Calculator;
sub new {
my $prototype = shift;
my $class = ref($prototype) || $prototype;
my $self = {};
bless( $self, $class );
return $self;
}
This gets typed so often that it's typically shortened beyond (IMHO) legibility to
Code:
package Calculator;
sub new {
my $proto = shift;
return bless( {}, ref($proto) || $proto );
}
</overcomplication>
Now lets add some methods. Remember that the first argument to each of these will be the Calculator object against which they are called, so the call
will call our initialise method with two arguments: $calc and 7.
It's an easy method, as is the result() method. One sets a value in the hash, the other returns it:
Code:
package Calculator;
sub new {
my $proto = shift;
return bless( {}, ref($proto) || $proto );
}
sub initialise {
my $self = shift;
$self->{Value} = shift;
}
sub result {
my $self = shift;
return $self->{Value};
}
The numeric functions are similarly trivial:
Code:
sub add {
my $self = shift;
$self->{Value} += shift;
}
sub multiply {
my $self = shift;
$self->{Value} *= shift;
}
We've got no error-checking or reporting and we've missed a couple of good tricks on the way, but we've got a working calculator object. You can put it in the same file as it's test code like this:
Code:
#!/usr/bin/perl
my $calc = new Calculator;
$calc->initialise( 7 );
$calc->multiply( 2 );
$calc->add( 6 );
print $calc->result(), "\n";
package Calculator;
sub new {
my $proto = shift;
return bless( {}, ref($proto) || $proto );
}
sub initialise {
my $self = shift;
$self->{Value} = shift;
}
sub result {
my $self = shift;
return $self->{Value};
}
sub add {
my $self = shift;
$self->{Value} += shift;
}
sub multiply {
my $self = shift;
$self->{Value} *= shift;
}
There you go - a ten-minute object. Perhaps you might like to give it more complicated methods such as [tt]the_number_you_first_thought_of()[/tt] - you'd need an extra attribute (hash key) to store the intialisation values - or named registers with store() and recall() methods.
You could give the constructor an initialisation argument.
You could arrange for every method to return the invoking object so that you could say
Code:
my $calc = new Calculator( 7 );
$calc->add( 7 )
->multiply( 6 )
->store( 'tmp' )
->initialise( 3 )
->multiply( 4 )
->add_from_recall( 'tmp' ) );
print $calc->result(), "\n";
I've just realised that the store and recall syntax is a bit broken, but then I didn't spend much time on it!
As with this trivial example, the methods of real-world object often turn out to be very easy to write. In fact, when they are not, it's often a sign that the original design wasn't sufficiently thought through (
as in the store() and recall() debacle above, where I've sentenced myself to writing second versions of every arithmetic method that use the recalled value instead of one passed as an argument. Alarm bells ring and I go back to the whiteboard).
If this has been useful, I can cover object inheritance in a similar fashion.
f
["]As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.["]
--Maur