![]() ![]() |
![]() ![]() |
![]() ![]() | |
© 1997 The McGraw-Hill Companies, Inc. All rights reserved. Any use of this Beta Book is subject to the rules stated in the Terms of Use. |
If there was a theme to this book, aside from the obvious one of teaching Perl programming, it would be that the process of truly learning a computer language is learning how to 'scale up' that computer language to do larger, and more complicated projects.
In fact, the same thing could be said about the whole of computer history. One of the main motivations in computer science is to scale up projects: to make larger and larger projects, for less and less money. Right now, all the research in software engineering points to the object as the best way to make the largest projects and to make them cheaply.
Therefore, the rest of this book will be devoted to how to make effective objects in Perl: object syntax, object oriented techniques, useful object code, and finally, a few examples of object oriented applications. This chapter will be a starting point: we will concern ourselves with the syntax of objects. For now, don't worry about the theory behind objects, or object oriented techniques. We shall cover plenty of that later!
If you are familiar with Perl object oriented programming, you might want to skim this chapter. Others will want to at least read it once, and then come back to it again and again as they get familiar with objects.
Once you get used to the concepts and syntax presented in this chapter, you are strongly advised to check out the perltoot, perlobj, and perlmod documentation provided in the Perl distribution. perltie also helps, if you want to do what is called 'tie'ing ( a certain type of object which we briefly discuss below).
This chapter serve the same purpose as Chapter 14 did for modules: to give a thorough grounding in the syntax of objects, and then take that basis and implement some simple examples. We will cover a lot of territory here. First, we cover object principles, giving an idea of the viewpoint which Object Oriented programming imposes on programming.
Second, we take these object principles and consider how they look implemented in Perlish syntax. We look at each piece of the object, and see how the pieces are implemented. Third, we take a look at the difference between the class and the object, and discuss how this difference will affect how you program.
Fourth we consider inheritance, the much misunderstood programming practice which lets you reuse code by getting it from another, related 'parent' module. We also see the syntax for implementing it.
Last, we consider overloading, which lets you treat symbols ('+','-', etc., etc.) differently with the different objects that you implement. We see the syntax for doing overloading plus some examples. Along with this we look at 'tie'ing, a Perl-specific form of overloading which allows you to make hashes, arrays, or scalars 'objects in disguise'; objects that look like a Perl data structure but actually do programmable things.
So, let's look first at a more rigorous view of what makes up an object. We have been hinting at this for long enough; let's flesh it out a little more.
Those of you who are new to object oriented programming should remember that object oriented programming is not rocket science. It is simply a shift in viewpoint that takes a bit to get used to. Now you look at code as being composed of several objects which have their own data and functions, rather than being composed of several statements and subroutines which you explicitly write inside your scripts. In order to get the task done, you call an objects' functions, and those objects do the job for you.
This is rather abstract, so let's consider a simple example: a clock. Now, a clock is an object in the common sense of the word, but it is also makes a good example of a computer object. A clock has three elements:
1) data: A clock contains the current time, sometimes the current date, and so forth.
2) methods or functions to get at and manipulate that data: a clock has a dial for reading the time, and buttons for setting the time, maybe a dial for winding it.
3) elements that make the clock work: these are the dials, the gears, the verge and foliot, and the interplay between them. These might be called the algorithms of the clock.
Objects in the computer sense are very much the same. In Perl, objects are implemented in special modules called classes. Classes create objects, and the objects that they create have three very similar elements in them:
1) object data: This is data that the object holds. It is what distinguishes one object from another object of the same type.
2) object methods: These are functions that manipulate the data that the object has, or return data from the module, or any one of a number of things. They are the constant for objects of the same type.
3)object algorithms: These are the algorithms that are used to implement the object's methods.
In addition, objects have the following special functions:
1) a constructor or constructors: Constructors are used to make the object, to set the data which the object will later use.
2) a destructor: Destructors are functions that destroy your objects when they are done with them. They are implicitly called when the object goes out of scope or the program ends.
We shall take a look at each of these elements in detail later. But first, let's take this a little bit further. Let's implement a clock class in Perl (again, classes are the modules that implement objects). Its not going to do very much, but it will illustrate the concepts, especially if you are a beginning Perl object programmer:
1 package Clock;
2
3 sub new
4 {
5 my ($type) = @_;
6 my $self = {};
8 $self->{time} = time();
9 bless $self, $type;
10 }
11 sub get
12 {
13 my ($self) = @_;
14 return($self->{time});
15 }
16 sub set
17 {
18 my ($self, $secs) = @_;
20 $self->{time} = $secs || time();
21 return(1);
22 }
As I said, this does not do very much, but it illustrates a great deal about how Perl objects work. Our object is called $self in the class,and it has one data member in it: time. It has two methods: get, which gets the time, and set, which sets the time. It has no destructor. This clock is frozen at the point of creation, and only shows its time in seconds since January 1, 1970.
But it is an object, and 75% of your objects will be advanced versions on this simple model. Let's see how you might use this object in a script of yours:
1 use Clock;
2 my $clock1 = new Clock();
3 bigNastyHairySubroutine();
4 my $clock2 = new Clock();
5
6 print "Big Nasty Hairy Subroutine started at ", $clock1->get(), "\n";
7 print "Big Nasty Hairy Subroutine ended at ", $clock2->get(), "\n";
8 print "Big Nasty Hairy Subroutine took ", $clock2->get() - $clock1->get(), "seconds";
All of the pertinent object calls are bolded here. Line 2 creates a Clock (called $clock1) before the 'Big Nasty Hairy Subroutine'. This calls the subroutine new inside the class Clock: our constructor. It also so happens to freeze the time at which it was called into the element: $clock1->{time}..
Line 3 calls the big, nasty, hairy subroutine, and line 4 makes another Clock object, this time called $clock2. Lines 5, 6 and 7 use the method get of the Clock object to retrieve the times associated with those clocks and print out how long the subroutine took.
Simple, but there are a lot of mechanics behind how this is actually working. Let's now take a closer look at the way that Perl allows you to make objects in this fashion. Perl's mechanism to do this is quite elegant. Knowing how it works will contribute quite a bit to your understanding of the language. As said, there were five parts to a simple class and its associated object: the constructor, the object methods, the object data, the object algorithms, and the destructor.
Let's take each element in turn.
The constructor is the first required part of an object. The constructor is used to make an object when needed. To make a constructor, simply put a function in your class that returns what is called a blessed reference to the main routine. A blessed reference is designated by the keyword bless. Its purpose is to set the type of the reference based on the type of the object called.
In the following,
my $self = bless {}, 'Object';
$self is a blessed reference of type Object.
An example is quite useful here to show how all this works. Figure 16.1 shows the clock example again, this time with a little bit of a play-by-play description and some added features to make the example more universal:
fig161.fig
Figure 16.1
The Clock Constructor
What's going on here? There are three major points that we should cover:
First of all, notice that we have done a little bit of a 'switcheroo' on the arguments here. The statement:
my $clock1 = new Clock('argument1');
becomes the subroutine call:
Clock::new('Clock', 'argument1');
In other words, the first argument is the name of the Class!*
**** Begin Note*****
Why? The answer has to do with inheritance and in particular multiple inheritance, the over-hyped, much-feared object oriented technique which we shall go over later on.
You will never want to use the 'Clock::new('Clock', 'argument1') form. Ever. This is because of the inheritance principle which we shall talk about later. For now, simply store this principle in the back of your brain, do the mental gymnastics to turn new Clock('argument1') to Clock::new('Clock', 'argument1') and you will be fine. And if you want more detail on why, check out inheritance below.
*
You will get weird runtime errors if you forget to put the type as the first argument in your constructor and do something like this:
1 package Clock;
2 sub new
3 {
4 my (@arguments) = @_; # $arguments[0] is the TYPE of class
5 my $self = {}; # we are dealing with. NOT the bolded element
6 # $clock = new Clock('a')
7 return(bless $self, 'Clock'); # VERY BAD!!!! You have hard-wired what type $self
8 # is. What happens when you want to use inheritance?
9 }
This is a major 'gotcha'; the above looks valid and creates a Clock, but will bite you later when you get to inheritance. It takes a long time to get used to the 'extra argument', but if you do so, it will pay you off many times.
The statement 'my $self = {}' shows the fundamental starting point of the object, and of all objects. All objects in Perl, complicated or simple, are composed of a simple reference. They continue to be simple references as long as they are alive.
What does this mean? When you say:
my $clock2 = new Clock('');
instead of saying:
$clock2->get();
to return the time associated with that clock, you could instead say (although ill-advised):
$clock->{time};
You can reach directly into the object itself and get the data associated with it, without going through the method to get it. This is called breaking encapsulation, and it is a bad practice indeed. Objects are meant to be inviolate and 'untouchable'; you are supposed to be going through an interface and here you are, reaching in to grab the data directly! But it does show the nature of Perl's objects quite well to see their 'bare nature' as references.
Objects can be made any type of data reference: hash references, array references, and scalar references. Hash references are the most common since you can have many, named data elements in them, but sometimes it is helpful to have scalars instead.
Note the line:
bless $self, $type;
This is the last line in the constructor, and the value that comes out of it will be returned to the subroutine. So what is this doing? This call translates into:
bless $self, 'Clock';
When you then print out $self, you get something that looks like:
Clock=HASH(0x9d450)
This indicates that $self is both a hash reference and a Clock reference. Bless is therefore indentifying ('tagging') $self as being a clock. $self is returned to the main program in:
my $clock = new Clock();
Then, $clock is printed out, it will also say 'Clock=HASH(0x9d450)'. The 'Clockness' is carried by the reference, it is therefore persistent, and will be transferred to any copy of the reference that you happen to make.
All of these points will bear heavily on what we discuss next: the object methods, and how they work.
Just as the object's constructor has two parts, so does the object's methods: define and call the method.
Figure 16.2 shows these two parts in tandem and how they work together. Again the clock example is extended to be more universal:
fig162.fig
Figure 16.2
An Object Method, and How It Works
Again, there are three points to consider here.
The first thing to notice here is that there is the same, basic mismatch between the arguments. There is one argument to get() when we call the method, but two arguments when we actually write the method.
As stated, the first argument is the object itself. In other words, when you say:
$clock->get('argument1');
you are, in effect, saying:
Clock::get($clock, 'argument1');
Assuming, of course, that $clock is a 'Clock' object. Why? Well, the method somehow needs access to the data inside the object. And, since an object in Perl is simply a blessed reference, the way Perl does this is by passing the object reference around every time it sees something of the form '$self->method();'*
This argument chicanery, passing the object itself as the first argument, was done for two reasons. (Instead of having some 'magic' that hides the object argument.) First, it makes things much more flexible and explicit when you write your methods. You can chose extremely unorthodox object models when you really get fancy with Perl object orientation. Second, it provides a much more consistent interface for functions. Instead of having one type of function for methods, and another for regular functions, Perl uses the same mechanism for both. |
Remember blessing? Well, when you say something like:
bless $clock, 'Clock';
then Perl has tagged $self as being of type 'Clock'. But what does this mean? Well, this has everything to do with how Perl actually finds the methods when you say something like:
$clock->get();
This logic is more thoroughly spelled out in Figure 16.3:
fig163.fig
Figure 16.3
A Reference Finding an Object Method
Let's take this step by step. When you say
bless $clock, 'Clock';
print $clock;
this will print the reference which is $clock.
'Clock=HASH(0x9d450)'
Hence, when you say something like
$clock->get();
you are actually saying
(Clock=Hash(0x9d450))->get();
Perl then knows the type of reference that it is, and therefore, opens the correct function to go along with it. Again, it passes the object as the first argument:
Clock::get($clock);
And then the function is actually called.
These two points summarize how Perl actually finds the correct method to call, and then what functions it uses to actually call it. Of course, it is not necessary to memorize this logic to actually program classes and objects.
But it sure doesn't hurt. I don't know how many times that I have forgotten to put "$self" at the beginning of a method, only to be bitten by it later, or how many times I have paused to act since I wasn't sure what mechanism Perl used to perform its magic.
Well, now you know. And it will pay dividends when we get up to how Perl does inheritance.
Object Data is fairly simple in Perl. Since objects are a simple reference (hash, array, or otherwise), there is no reason to declare object data in Perl as is necessary in most other OO languages (like C++).
Consider, again, our 'Clock':
1 my $clock = new Clock();
2 print $clock->{time};
3 print $clock->get();
Here, lines 2 and 3 do exactly the same thing. The call to $clock->get() is really a wrapper around a hash call which returns '$self->time()':
1 package Clock;
2
3 sub get
4 {
5 my ($self) = @_;
6 return($self->{time});
7 }
On the face of it, it seems that the form '$clock->get()' does not gain anything compared to $clock->{time}. It is slower, and longer, to write.
However, it does have one benefit over $clock->{time}. It is much more flexible. If you change how the Clock object itself tells time, then you can make the method get a lot more complicated. Whereas $clock->{time} cannot be enhanced in this way.
Again, here is an example of how encapsulation is your friend and why we devoted the next chapter to it. Encapsulation allows you to scale up your projects by 'divorcing the usage from the implementation'. We shall have a lot to say about this next chapter.
The way you implement Classes has a large bearing on how useful their objects will be. The one golden rule about how you design your Classes is:
You can change the implementation of your objects, but by all means keep the interface the same.
The point is that if you change the way a method is written, but leave the interface alone, you will only have to change one piece of code: the method itself. If you change the way programs call that method (or the results that are returned back) then you have to change code everywhere that method is used.
Let's look at our clock again. Suppose that we now have thousands of programs using clocks with the statement:
my $clock = new Clock();
my $time = $clock->get();
Now, one day, we decide that we want to have our clock have the ability to return seconds, hours, days, or years since 1970. Our algorithm right now is:
sub get
{
my($self) = @_;
return($self->{time});
}
If we enhance this by adding a parameter, as in
1 my $conversion = {
2 'seconds' => 1, 'minutes' => 60,
3 'hours' => 3600,'days' => 86400,
4 'years' => 31557600
5 };
6 sub get
7 {
8 my ($self, $type) = @_;
9 (print ("Unknown type $type!\n"), return(undef))
10 if (!defined $conversion->{$type});
11 return(int($self->{time}/$conversion->{$type}));
12 }
then consider what happens to all of the programs who use get:
1 my $clock = new Clock();
2 my $time = $clock->get();
Line 2, which used to get the number of seconds since 1970 will now return 'Unknown type $type!'. By making this one change, you have broken thousands of programs.
The solution? Well, here it is easy. All you have to do is insure backward compatibility by adding a workaround. We could either
a) make a new function, and call it newget(). That way, we need not worry about backward compatibility.
b) alter the old function to have a default. The default so happens to be the old data.
Sometimes the first course is the right way to go, sometimes the second course is correct. It depends on the situation. In this case, let's alter the function to be backward compatible:
1 my $conversion = {
2 'seconds' => 1, 'minutes' => 60,
3 'hours' => 3600,'days' => 86400,
4 'years' => 31557600
5 };
6 sub get
7 {
8 my ($self, $type) = @_;
9 $type ||= 'seconds';
10 (print ("Unknown type $type!\n"), return(undef))
11 if (!defined $conversion->{$type});
12 return(int($self->{time}/$conversion->{$type}));
13 }
Note the new line 9 (in bold). Here, our workaround is simply to make the default type that we get from our clock the number of seconds. That way, any existing programs will still get the number of seconds when they pass no arguments to get.
The old interface has therefore been preserved. However, new programs that use the Clock object are able to say $clock->get('hours') with no strings attached.
Of course, this isn't a rule to be set in stone, but it is useful when your projects get more stable, and you get a large body of code. Perhaps it should be called 'only change your interface when necessary'. Sometimes, the cost of keeping the interface constant outweighs the cost of changing it.
But in general, the larger the project, or the more clients your program has, you change your interface at your peril. This is an important point, and will shadow a lot of the design we do later.
The Destructor
Destructors are methods which occur when objects are 'destroyed'. This occurs either by garbage collection (going out of scope) or when the program exits. Perl typically calls these for you, so you don't see them in the program.
You can make a destructors in Perl, although they are not nearly as important in Perl as they are in languages like C++ and Smalltalk. This is because the traditional role of the destructor is to free up memory when the object is no longer needed, and Perl handles memory management for you.
As it stands now, we need no destructor in our Clock object to clean up after us. Nonetheless, let's make the Clock an 'alarm clock' which 'goes off' when it is being destroyed:
package Clock;
sub DESTROY
{
my ($self) = @_;
print "Destroying the clock now!\n";
}
Now, when we use the clock in a program, by saying:
destroy1.p:
1 use Clock;
2 my $clock = new Clock();
or
destroy2.p:
1 use Clock;
2 use strict;
3 my $xx;
4 for ($xx = 0; $xx < 10; $xx++) { my $clock = new Clock(); }
we implicitly are calling the destructor, which prints out 'Destroying the clock now!' one time in 'destroy1.p' and ten times in destroy2.p.
Typically, in Perl, you will only need a destructor when a non-data element is in your object. This non-data element could be a file, a database connection, a Web connection, and so on. Since most of your objects will be data objects, you won't need a DESTROY function too often.
Basic Object Principles Summary
As you can see, there is quite a lot going on behind even the simplest object in Perl, and you are well advised to practice writing small classes of your own - and knowing how they work - before diving into more complicated stuff. At the very minimum learn how the following five things:
Object Constructor: Know that my $clock = new Clock('argument') turns into Clock::new('Clock', 'argument') to construct objects.
Object Methods: Know that $self->get('argument') turns into Clock::get($self, 'argument'). If possible know the mechanism how.
Object Data: Know that an object in Perl is also a reference, and hence all the data is held as members inside that reference.
Object Algorithms: Know that it is important to write your object's algorithms so that the interface to the outside world stays relatively the same.
Object Destructor: Know that the special function sub DESTROY is called when the object gets garbage collected.
Also realize that when you are doing object oriented programming, you have two things that you need to worry about: the object itself, and the programs that use the object. A good object oriented project always takes these two elements in account.
Intermediate/Advanced Object Principles
Don't forget that you can do a lot with even the so-called 'Basic' object principles. Perl has support in its syntax for the following two types of objects:
A specific syntax for inheritance
A specific syntax for different types of overloading
However, most of the complicated object designs in Perl come from the flexibility of its syntax. You can do marvelous things with the simple syntax that Perl gives you. In fact, a large part of the rest of the book is devoted to proving this!
So let's take a look at how Perl does its inheritance, what exactly is meant by 'tie'ing, and one issue that we kind of ignored above: the relationship between class and object.
Class vs Object
In object oriented programming, there are two concepts that are widely misunderstood: the Object and the Class. In fact, it is easy to get the two mixed up with one another, and the tendency is to use them interchangeably.
The first thing to realize is that they are not the same. The Class can be seen as the container for the object. Let's take another look at the clock example:
1 package Clock;
2
3 sub new
4 {
5 my ($type) = @_;
6 my $self = {};
8 $self->{time} = time();
9 bless $self, $type;
10 }
11 sub get
12 {
13 my ($self) = @_;
14 return($self->{time});
15 }
16 sub set
17 {
18 my ($self, $secs) = @_;
20 $self->{time} = $secs || time();
21 }
This class contains the functions/methods new, get, and set. These functions never change. However, the object in this picture ($self) does change from instance to instance. Hence the relationship between the Clock object and the Clock class can be seen something like what is shown in Figure 16.4:
fig164.fig
Figure 16.4
Relationship between Object and Class
This relationship is a one to many relationship: one class, many objects. The class consists of items which are automatically shared between objects, and the object consists of items which are unique.
This means that we can use this relationship to our advantage. If we want functions that do something in common for all objects, then we make what is called a class method. If we want data that is shared between objects, we make what is called class data.
Class Data
Perhaps an example will make this distinction clearer. Remember our proposal to make the Clock class be able to give back not only seconds, but minutes, hours, etc.? The code looked like:
1 package Clock;
2
3 my $conversion = {
4 'seconds' => 1, 'minutes' => 60,
5 'hours' => 3600,'days' => 86400,
6 'years' => 31557600
7 };
8
9 sub new
10 {
11 my ($type) = @_;
12 my $self = {};
13 $self->{time} = time();
14 bless $self, $type;
15 }
16 sub get
17 {
18 my ($self, $type) = @_;
19 $type ||= 'seconds';
20 (print ("Unknown type $type!\n"), return(undef))
21 if (!defined $conversion->{$type});
22 return(int($self->{time}/$conversion->{$type}));
23 }
24 sub set
25 {
26 my ($self, $secs) = @_;
27 $self->{time} = $secs || time();
28 }
The emboldened code is added; the hash reference $conversion is what we are concerned with here. It is the perfect example of class data.
Since $conversion is defined at the top, outside of a function, it does not go with any given object (member of the class) but instead is used as a translator which converts the $type which is passed to get (seconds, minutes, hours, etc.) into the number of seconds. This translator can be used in any object.
Class Methods
The other type of shared class resource is a function which affects all objects. For this, let's consider a clock function that we can use to resynchronize all of the clocks to the current time.
How would we do this? Well, let's alter the constructor of our clock so that it makes a copy of the object onto a central stack:
1 my @objectStore;
2 sub new
3 {
4 my ($type) = @_;
5 my ($self) = {};
6 push (@objectStore, $self);
7 $self->{time} = time();
8 bless $self, $type;
9 }
Here, then, in line6 @objectStore keeps a record of all the clocks that we have made. It is class data, again, and this means that it is common to all objects.
Our resync function will then use this 'universal' information to synchronize each of these objects in an orderly fashion:
1 sub resync
2 {
3 my ($time) = @_;
4 my $clock;
5 foreach $clock (@objectStore) { $clock->set($time); }
6 }
Here, line four actually reaches into each one of the clocks that we have 'registered' in the constructor, and then sets it for us. Hence, if we said in a client:
1 use Clock;
2 my $clock1 = new Clock();
3 sleep(60);
4 my $clock2 = new Clock();
5 Clock::resync(time());
6 print $clock1->get() - $clock2->get();
Then line 5 is the class method call. It sets all the clocks to exactly the same time. The current time is evaluated, passed to 'Clock::resync()' which then in turn passes it to 'Clock::set()' which then in turn sets each clock to the same time.
There are some cool things that you can do with Class methods and Class data, and by understanding the difference between Class and Object. We shall see more examples of this as we go. One of the primary uses for this is to construct 'Configuration' hashes which tell what the default behavior is for an object.
Class Methods, Part II
A quick side note, but you don't need to use the format 'Class::method()'. You can use the format:
Class->method();
instead. Why would you want to do this? Well, when we get to inheritance, we shall find that the class 'Class' can inherit subroutines from other classes, so 'subroutine()' may or may not come from the class 'Class'.We shall get to how inheritance works later this chapter.
These forms, therefore, are not exact synonyms for each other. I don't use inheritance much, but if you are going to do so, use the 'Class->method()' form instead. 'Class::method()' ties you down to only using methods that are found in 'Class'.
Summary of Class vs. Object
Class and Object are two concepts which get routinely mixed up by people who are just beginning to learn OO programming. Classes can be thought to contain objects, the one class containing the many objects. To make objects which share data, or a function which acts on one of the objects inside that class, make a class method or class data to do so.
This is done by making functions or data which aren't connected with any particular object. Figure 16.5 gives a short template on this:
fig165.fig
Figure 16.5
A Typical Class with Class Methods and Data
Inheritance
As we said earlier, inheritance is one of those things that people seem to glom onto when they first start to get into object oriented programming. This is rather a pity, since inheritance is, I think, way overblown as a technique. If you don't know what you are doing, inheritance can really mess you up.
That said, there is a bit of trivia that is cool here. C++, SmallTalk, and Perl are said to be Object-Oriented since they support inheritance. There are, however, true Object languages (Ada is one of them) and they get along perfectly fine without inheritance. So take the following with a grain of salt.
We shall also cover inheritance in a lot more detail, later (we devote a chapter to it), but for now, let's take a look at the syntax of how it works in Perl.
So what is inheritance? Inheritance is the practice of re-using code by defining a particular specific type of class in terms of a more generic one. An inherited class (or sub class) then uses the code that has been 'abstracted' out, into what is called a base class. Figure 16.6 shows the basic relationship:
fig166.fig
Figure 16.6
The Inheritance Relationship
Suppose we want to make two different types of clocks. One which gets time by looking at the sun (Clock::SunDial), and one which gets time by the standard means of asking the operating system what the time is (Clock::System).
We could then set it up so that we have one base class which contains all the functions in common, and sub-classes which contain what is unique between the two types of clocks.
The Base Class
Now, as said, we want to make it so both of them have the same interface (getting the number of seconds since 1970). Since the two different types of clocks have something in common, and we shouldn't have to duplicate code for each one.
What do they have in common?
We can include:
The function get since we have mandated that both types of clocks return the same value (the number of seconds since 1970) in the same way.
The constructor new since we can define the construction of the clock in terms of setting the clock.
What we find in Common, we put in 'Clock.pm'. This is called the base class:
1 package Clock;
2
3 my $conversion = {
4 'seconds' => 1, 'minutes' => 60,
5 'hours' => 3600,'days' => 86400,
6 'years' => 31557600
7 };
8
9 sub new
10 {
11 my ($type) = @_;
12 my $self = {};
13 bless $self, $type;
14 $self->set();
15 $self;
16 }
17 sub get
18 {
19 my ($self, $type) = @_;
20 $type ||= 'seconds';
21 (print ("Unknown type $type!\n"), return(undef))
22 if (!defined $conversion->{$type});
23 return(int($self->{time}/$conversion->{$type}));
24 }
Note that we have done something tricky here: in line 14 we have defined our constructor, new with the object method set. We don't know at this point what set is going to look like; it will differ with each sub-class. We then say in the constructor "call the set function, whatever it is." With inheritance, 'magic will happen', and the correct set function will be called.
sub classes and @ISA
Now that we have the base class, we need to ponder what we need to put in the sub-classes. The only function left is set, which differs for the 'System' clock and the 'SunDial'. Implement both versions of set, and store them in their own files:
Clock/System.pm
1 package Clock::System;
2 use Clock;
3 @ISA = qw(Clock);
4 sub set
5 {
6 my ($self, $secs) = @_;
7 $self->{time} = $secs || time();
8 }
Clock/SunDial.pm
1 package Clock::SunDial;
2 use Clock;
3 @ISA = qw(Clock);
4 sub set
5 {
6 my ($self, $secs) = @_;
7 $self->{time} = $secs || lookAtSun();
8 }
9 sub lookAtSun
10 {
11 print "Looking at the Sun for the time!\n";
12 return(time());
13 }
Two things here: one, note the special phrase '@ISA = qw(Clock)'. This is a key variable used for inheritance. It indicates that 'Clock::SunDial' IS Also a Clock, and if a method/constructor/destructor is not found inside 'Clock::Sundial', Perl then looks inside 'Clock' to find it. If Perl can't find it there, Perl looks through the next member of @ISA, and so on. If Perl runs out of @ISA entries to look in, it fails.*
**
****
Not entirely true. See the section on 'UNIVERSAL'. *** |
Figure 16.7 shows the path which is taken in this case, 'my $sundial = new Clock::Sundial()':
fig167.fig
Figure 16.7
Perl Using Inheritance to Determine the Constructor
Let's take this step by step.
First, Perl looks for the constructor new inside 'Clock::Sundial'. It can't find it, so it then looks in '@ISA' and notes that a 'Clock::Sundial' is also a 'Clock'.
Second, Perl looks for the constructor new inside 'Clock'. It finds it there, so the statement.
my $sundial = new Clock::Sundial();
is translated into
my $sundial = Clock::new('Clock::Sundial');
Remember the oddity about having the type be the first argument to the constructor? Here is where it is used most heavily. The 'Clock::Sundial' may use the 'Clock's constructor, but since the type of clock is passed to the 'Clock' constructor, Clock::new() will create a 'Clock::Sundial' instead of a 'Clock'.
This is important to understanding how Perl actually calls the correct method. Figure 16.8 shows how Perl determines the correct method to call:
fig168.fig
Figure 16.8
Perl Using Inheritance to Determine a Method
Again, '$clock->get()' translates into something like (Clock::SunDial=Hash(0x1f476))->get(). From there, we know that $self is a 'Clock::SunDial', and since 'Clock::Sundial' doesn't have a get method, it looks in 'Clock'. Hence,
$clock->get();
translates to
get($clock);Clock::
If we said $clock->set(); on the other hand, this would translate into:
Clock::SunDial::set($clock);
because Clock::Sundial has a set method.
No need not worry about speed with all of these lookups: Perl caches the result of the lookup in a lookup table so it doesn't do the same work each time. Likewise, you don't have to worry about defining two versions of set (one for the base class, and one for the sub class) and then have them conflict with each other. Perl always picks the first method it finds.
The base method acts as a sort of default. If you decide that you need a specialized method for a given sub class, then Perl will always use that, and never use the base method again.
There are a couple of minor concepts to know when dealing with inheritance. They are covered below, and when you get really fancy, you might consider exploiting using them in your own class.
Remember when we said that if an object runs, and can't find a method in @ISA, it fails? Well, there is an exception to this.
There is a special class called UNIVERSAL, which all objects inherit from. It has the following methods, which are helpful when dealing with versioning and what not. It contains the following methods:
isa - tells whether or not a reference is a certain type. $self->isa('class') returns 1 if $self is a class, and '0' if it is not. It does a search through the @ISA variable for you.
can - tells whether or not a reference can do a given method. $clock->can('get') would say if $clock had a get method. If so, it returns a reference to the correct subroutine.
VERSION - we went over this last chapter. Clock->VERSION('1.00') will go into the Clock package and see if it is at least version 1.00 or better.
This is just a quick summary of what the online documentation says in much more detail. Look at 'UNIVERSAL.pm' in your favorite editor, or look at the Perl modules documentation. Both have much more to say about this.
If you have two methods that have the same name in an inheritance relationship, then the one in the subclass will be executed first. In code terms, the object call:
my $self = new SubSubClass();
$self->get();
with SubSubClass being defined as:
package SubSubClass;
use BaseClass;
use SubClass;
@ISA = qw(SubClass BaseClass);
will first try to translate $self->get() to:
SubSubClass::get($self);
then
SubClass::get($self);
then
BaseClass::get($self);
then
Universal::get($self);
and then finally die if it cannot find the method get in any of these places. Now what happens if you have a SubSubClass::get() defined, but still want to call SubClass::get() instead?
This is what 'SUPER::' is for. If you say:
package SubSubClass;
@ISA = qw(SubClass, Base);
sub get
{
$self->SUPER::get()
}
Then Perl will ignore SubSubClass::get(), and then look 'up' the @ISA chain to find the next get() method (starting at SubClass::get()).*
*
*
Note that
has to be in the package SubSubClass itself. If you say:
then this won't do what you want. Perl tries to look for the next 'get()' function from the point of view of the package that you are in. See the online documentation for more detail on why this is. * |
'SUPER::' is termed a pseudo class. It allows you to call methods that are not directly inherited, but are still there. 'SUPER::' is seldom used, but when you do use it, it is invaluable.
Sometimes you may want to define a sub class method in terms of a base method. For example, suppose that you wanted to log each time your clock was used, each time you 'set' it. You call this sub-class 'Clock::System::Log', and set it up as such:
Clock/System/Log.pm
1 package Clock::System::Log;
2 use Clock::System;
3 use Clock;
4 use FileHandle;
5 @ISA = qw (Clock System Log);
6 sub set
7 {
8 my ($self, $secs) = @_;
9 $self->SUPER::set($secs);
10 my $fd = new FileHandle(">> clocklog");
11 print $fd "Clock set at ", $self->get(), "Seconds";
12 }
In line 9, then, we set up the set method defined in terms of 'Clock::System::set()'. We simply append the extra stuff that we are going to do with this particular version of 'clock' (like open a log and append onto it the time with which it was set.)
When we say:
my $clock = new Clock::System::Log();
$clock->set();
then, Perl goes through a flurry of activity to find which set method that you mean. But the main point here is that by saying 'SUPER::' you don't need to explicitly state which method you want, you don't have to know that a set method is in Clock::System. Perl just does this automatically for you.
Inheritance is a relatively complicated technique that you should only attempt if you really know what you are doing. Perl supports inheritance, but there is a quite a bit of logic that you need to know before you'll understand what exactly is happening when you program. You should understand:
The inheritance relationship: the fact that a subclass inherits methods from the base class, and that the subclass goes through a bit of syntactical gymnastics to find which method to execute
@ISA -- the @ISA variable is the main way that you do inheritance. @ISA = ('Base','Super'); means that the class that you are working in is a 'Base' and a 'Super' class as well.
Understanding the syntactical gymnastics with inheritance helps too. Perl does it for you, but if you understand exactly what Perl is doing for you (or to you!) will make it much easier to handle any problems that you might have.
We are getting into rather esoteric territory with this subject. You probably don't even want to approach overloading until you've gotten a good handle on the basic types of objects. In fact, overloading is sort of a side road in the road network that is objects, and you can get along without it quite well.
Overloading refers to taking a function that is represented by a symbol ('+', '-', '%','/',etc.) and giving it a function that is meaningful to a class.
What does this mean? The classic example is a class which implements complex numbers. If $a = '1+2i' and $b = '2+3i', then you obviously want $a+$b to equal '3+5i'. This means overloading the plus operator so that it behaves differently when dealing with a complex number object.
There are two types of overloading in Perl: overloading of symbols and overloading of data structures. The first form is done with the help of a special use directive called 'use overload'. The second form of overloading is called 'tie'ing.
Let's take the 'complex number' example a little bit further and define the plus and minus operations, as well as the quotes operations, for a complex number object.
First we need to write the complex object:
package Complex;
sub new
{
my ($type, $real, $imaginary) = @_;
my $self = {};
$self->{real} = $real || 0;
$self->{imaginary} = $imaginary || 0;
bless $self, $type;
}
Here, we simply make a constructor, and stop. This class will have no direct methods. We want to make an interface that looks something like this:
my $complex1 = new Complex(5,4);
my $complex2 = new Complex(2,3);
my $complex3 = $complex1 + $complex2;
print "$complex3\n";
The last line should print 7 + 7I if we have done it correctly.
This is the power of overloading. In some cases, especially mathematical ones, it vastly simplifies the interface. You could say:
my $complex3 = new Complex();
$complex3->add($complex1);
$complex3->add($complex2);
But why say that when
$complex3 = $complex2 + $complex1;
does exactly the same thing, albeit much clearer?
OK, now let's actually go and implement this package.
We start by declaring what symbols we are going to overload:
1 package Complex;
2
3 use overload "+" => \&plus,
4 "-" => \&minus,
5 "=" => \&equals,
6 '""' => \&print;
Here, each line indicates a callback which we shall use to substitute for that given symbol. Callbacks, again, are functions that are supplied to provide extra information that a module or object doesn't know about, or needs to know 'on the fly'.
In this case, every time the Perl engine sees '$a + $b', where $a and $b are objects of type 'Complex' it executes a '&Complex::plus($a, $b)' instead. When Perl sees a '$a - $b' it executes '&Complex::minus($a, $b)' instead. print "$a" triggers '&Complex::print($a)', and so forth.
So all we need to do here is fill in the blanks, and write some functions which do these various actions:
7 sub plus
8 {
9 my ($complex1, $complex2) = @_;
10 return (
11 Complex->new
12 (
13 $complex1->{real}+$complex2->{real},
14 $complex1->{imaginary}+$complex2->{imaginary}
15 )
16 );
17 }
'plus' then, will return the sum of the real parts and the imaginary parts, and create a new 'Complex' in the bargain. (Complex->new() is just a synonym, again for 'new Complex()')
18
19 sub minus
20 {
21 my ($complex1, $complex2) = @_;
22
23 return (
24 Complex->new
25 (
26 $complex1->{real}-$complex2->{real},
27 $complex1->{imaginary}-$complex2->{imaginary}
28 )
29 );
30 }
31
'minus' does the exact opposite..
32 sub equals
33 {
34 my ($complex1, $complex2) = @_;
35 $complex1 = new Complex($complex2->{real}, $complex2->{imaginary});
36 }
'equals' creates a new complex ($complex1) and then assigns it the values from the complex on the right hand side of the equation.
37 sub print
38 {
39 my ($complex1) = @_;
40 if ($complex1->{imaginary} < 0)
41 {
42 return "$complex1->{real} $complex1->{imaginary}i\n";
43 }
44 else
45 {
46 return "$complex1->{real} + $complex1->{imaginary}i\n";
47 }
48 }
Finally, 'print' handles situations of the form: print "$complex". Instead of print "$complex" doing the regular thing (turning $complex into a string) it executes the print command instead, substituting the text:
"$complex1->{real} $complex1->{imaginary}i\n";
for any complex reference that Perl goes to print. Figure 16.9 shows the relationship between the usage of overloading, and its output:
fig169.fig
Figure 16.9
Overloading - Relationship Between Usage and Arguments
All this said, exactly what can you overload? Well, the documentation will give you a complete picture of what you can do, but Figure 16.10 gives you a handy summary for what can get overloaded, and where the arguments transfer when you use them.
fig1610.fig
Figure 16.10
Overloading - a summary of usage.
If you want a real tour de force of overloading in action, you can look at 'lib/Math/Complex.pm' in the standard distribution. It fully implements the module that we only started above, and I can't think of a better real life example to look at. For what you can overload, the documentation in 'lib/overload.pm' is good in-depth report too.
Since 'use overload' provides you the ability to overload symbols like '+', '-' and so forth, then you can overload data structures by the use of the special function 'tie'.
Many objects act sort of like a glorified hash. We have already seen this in our 'Clock' object. In fact, the get function in our Clock was equivalent to a hash access:
print $clock->get();
and
print $clock->{time};
were exactly the same.
'Tie'ing takes this to its logical extreme. When you tie an object, you use the syntax of an hash, an array, or a scalar, but each time you use the syntax, a method call is called which actually does the work. This is accomplished by the tie command. You say:
tie(%hashname, 'ClassName',@argumentsToClassConstructor);
This makes an object by calling the constructor 'ClassName->TIEHASH(@argumentsToClassConstructor)'. (You can also make tied arrays, tied scalars, and tied Handles -- see the documentation perltie for more information on this).
This object is then accessible by the tied command. If you say:
my $object = tied(%hashname);
this retrieves the object created by the constructor ClassName->TIEHASH() and then puts it into object. When you are done with the tied hashes, you can untie them:
untie(%hashname);
which then destroys the object behind the hash in the process.
Let's give a simple example. Suppose you want a 'smart hash': one that keeps track of how many times a given key is accessed, stored, and deleted. This is the perfect application for tie. We shall make a package, 'SmartHash.pm', that we can use it in the following way:
1 use SmartHash;
2 tie (%hash, 'SmartHash');
3 my $hashObject = tied(%hash);
4 $hash{'a'} = 1;
5 $hash{'a'} = 2;
6 $hash{'b'} = 1;
7 print $hash{'a'};
7 delete $hash{'a'};
8
9 print $hashObject->get('deleted','a');
Line 2 actually takes the %hash object and ties it to the object SmartHash. What this then does is make an object associated with %hash that pretty much 'shadows' its every move. We get this hash in line 3 and call it $hashObject.
Now, when we set a key in the hash ($hash{'a'}) it calls the method $hashObject->STORE(). When we access an element in %hash (print $hash{'a'}) it calls the method $hashObject->FETCH() for us. When we delete an element it calls $hashObject->DELETE(), and so forth. Figure 16.11 shows all of the methods provided for hash 'tie'ing, what they do, and when they are triggered:
fig1611.fig
Figure 16.11
'Tie'ing Methods Provided for Hashes
Now, all we have to do is program each individual method in a way which makes it so the hash 'remembers' what has happened to it during its lifetime.
Let's start with TIEHASH:
1 SmartHash.pm
2 package SmartHash;
3 sub TIEHASH
4 {
5 my ($type) = @_;
6 my $self = {hashval => {}};
7 $self->{'accessed'} = {};
8 $self->{'stored'} = {};
9 $self->{'deleted'} = {};
10 bless $self, $type;
11 }
TIEHASH is the constructor for the SmartHash object. It is called when we first execute the tie command.
'tie(%hash, 'SmartHash');' tells Perl to go ahead and make a new object, SmartHash, and then tie it to the variable %hash, so when we access, change, delete, or do anything to %hash we immediately call a method inside this class. %hash is merely the frontend to the object that is created by this constructor.
As for our strategy in making a SmartHash, notice that we make three entries inside the object: one for each time an element is 'accessed', 'stored', and 'deleted'. We also make a place for actually storing the hash that we are to build; inside $self->{hashval}.
The FETCH method
Here is our FETCH method, the one called when we say something like 'print "$hash{orange}\n":
12 sub FETCH
13 {
14 my ($self, $key) = @_;
15 my $accessed = $self->{'accessed'};
16 my $hash = $self->{'hashval'};
17 $accessed->{$key}++;
18 return($hash->{$key});
19 }
In this case, 'orange' becomes $key in 'my ($self, $key) = @_', and the method stores that it in fact accessed 'orange' (line 17). It then retrieves the value of 'orange' from the hash and returns it (line 18).
The STORE method
STORE is the method called when we actually store a new value to the hash ($hash{'orange'} = 2, for example). Here's our STORE method:
20 sub STORE
21 {
22 my ($self, $key, $value) = @_;
23 my $stored = $self->{'stored'};
24 my $hash = $self->{'hashval'};
25 $stored->{$key}++;
26 $hash->{$key} = $value;
27 }
Here, we keep track that we have in fact stored the key 'orange' (line 25), before we actually store the value into the hash itself.(line 26)
The DELETE method
DELETE is the method called when we say 'delete $hash{"key"}'.
28 sub DELETE
29 {
30 my ($self, $key) = @_;
31 my $deleted = $self->{'deleted'};
32 my $hash = $self->{'hashval'};
33 $deleted->{$key}++;
34 delete $hash->{$key};
35 }
Again, this is exactly like a regular delete (line 34) but we keep track that we have deleted the key in the first place, storing the fact in $self->{'deleted'}(line 33).
The CLEAR method
CLEAR is the method that is called when we are clearing out the hash (something like 'undef %hash'):
36 sub CLEAR
37 {
38 my ($self) = @_;
39 my $key;
40
41 my $hash = $self->{'hashval'};
42 foreach $key ( keys %$hash ) { $self->DELETE($key); }
43 }
Again, since we are keeping a log of what has happened in the hash, we need to wrap up our delete calls in a way which record this activity. The easiest way to do this is by (in line 42) to call the DELETE method ourselves. This insures that we mark each 'delete' when it occurs.
If we had simply said 'delete $hash->{$key}', then no such marking would have occurred.
The EXISTS method
44 sub EXISTS
45 {
46 my ($self, $key) = @_;
47 my $hash = $self->{'hashval'};
48 return (1) if (exists $hash->{key});
49 return (0);
50 }
EXISTS is called when we say something like 'if (exists $hash{'key'});' It is questionable whether or not calling EXISTS is an access of that variable.. Here we assume it is not, and don't track it.
The FIRSTKEY and NEXTKEY methods
FIRSTKEY and NEXTKEY are used for looping through each element in a hash, as in 'keys(%hash)' and 'each(%hash)'. Here they are:
51 sub FIRSTKEY
52 {
53 my ($self) = @_;
54 my ($key, $value) = each(%{$self->{'hashval'}});
55 return($key);
56 }
57
58 sub NEXTKEY
59 {
60 my ($self, $lastkey) = @_;
61 $self->FIRSTKEY();
62 }
In other words, they provide the way to iterate through our tied hash, and get all the information out of it.
User Provided Methods
Whew! Now we have come to the end of the methods that we need to program in order for the 'tie'ing to work. In the process of stuffing the hash in the first place, we have insured that we have a 'log of activity' about what actually has occurred with the hash.
Now, we only have to figure out how to retrieve this activity log. We do it via get:
63 sub get
64 {
65 my ($self, $type, $key) = @_;
66 print "Undefined type!\n" if (!defined $self->{$type});
67 return($self->{$type}->{$key} || 0 );
68 }
Here, $type is one of three things: 'accessed','deleted', or 'stored'. A call to $self->get('accessed','phaedrus') will get the number of times that the key 'phaedrus' has been accessed.
This is the only user method that we have to define. It satisfies our purpose for retrieving the log information that we have stored. There are eight methods that are necessary to define how the 'tie'ing will work, and one method that is 'user defined'!
There is a lot of excess typing that you need to do in order to make 'tie'ing work. But when it is done, you have a pretty slick interface.
Usage
Enough typing. Let's now use the interface that we have developed. We can say:
1 use SmartHash;
2 tie(%myHash, 'SmartHash');
3 $myHash{'element1'} = 2;
4 $myHash{'element2'} = 4;
5 $myHash{'element3'} = $myHash{'element2'};
6 # ....
7 my $object = tied(%myHash);
8 print $object->get('accessed', 'element2');
This will print out '1'. We have accessed element2 one time since we tied the hash to an object.
The SmartHash has a lot of potential applications that we only touch on here. Suppose that you made a hash element for each link that you had on a CGI Web page. You could use the SmartHash to automatically figure out which of links were the most popular!
Or suppose that you have to figure out how often a customer is accessed in a database. Again, the smart hash can tell you at a glance which keys were accessed the most frequently.
Anyway, 'tie'ing has all sorts of uses. Below, we shall briefly point out one tied hash which is very popular: the ability to make hashes 'permanent', stored on disk.
Tie Example Number Two: DBM files.
No discussion of 'tie'ing would be complete without mentioning DBM files. DBM files allow you to manipulate a file on disk, as if it were a hash. You guessed it, they are implemented by 'tie'ing.
There are several types of DBM files (described in length in the online documentation). Each has their plusses and minuses. The one that we shall look at is the SDBM_file since it is portable, and the source actually comes with Perl. However, it is slow so you might want to use NDBM_file instead (the interface is exactly the same) or GDBM_file.
DBM usage
To tie to a DBM file, simply make a statement like:
use SDBM_file;
tie(%hash, 'SDBM_file', 'FileName', 1, 0);
Any writes or accesses to %hash will actually cause the file FileName to be written to and/or accessed. The hash is stored in this file for later retrieval. Hence,
$hash{'key'} = 'store';
will write to FileName the 'key,store' pair. When you want to access it you can say:
print $hash{'key'};
This is an easy way to share information between processes, and some implementations of DBM are very fast. The best, most supported, version of DBM is the Berkeley DBM which is included on the CD associated with this book.
DBM issues
However, don't get overzealous. One of the issues DBM's have, especially when sharing information between processes, is that you need to be scrupulous about locking your DBM files while other files read it.
The other thing to mention is that DBM files can become really big, which is part of their usefulness. When they become really big, you are probably going to want to traverse through them with the function each:
while (($key, $value) = each(%hash))
{
#
}
If you used the function keys with a large DBM file, you can end up eating a lot of memory, so be careful.
Summary of Overloading
The above is just a fraction of what there is to know about overloading (both overloading operators and overloading data structures), and you would do well to go back to the documentation for more detail.
However, the information discussed in this section is really the central knowledge that you need to know to use overloading:
1) overloading is simply the process of making an object use an expression like $c = $a+$b, instead of $c = $a->add($b);
2) 'use overload "=" =>\=' is an example of a statement which makes a class use overloading.
3) 'tie'ing is the process of making a data structure (hash, array, scalar, or Handle) be implemented in terms of an object to make it more flexible.
4) tie objects to data structures with 'tie(%hash, 'Object', @args)', and get the object they are tied to with 'my $object = tied(%hash)'. You can untie objects with 'untie(%hash)'.
5) you need to define eight methods that go along with 'tie'ing a hash: TIEHASH, FETCH, STORE, DELETE, CLEAR, FIRSTKEY, NEXTKEY, DESTROY.
Overloading is a cool thing to do, but it is not that widely used (I personally don't like to type that much!). You can stick with basic object programming, ignore inheritance and overloading, and do just fine.
To be fair, there is a lot of typing in overloading, but fortunately Perl provides two packages 'Tie::Hash', and 'Tie::StdHash' which let you inherit some of these methods by default. Go to Tie::Hash, and Tie::StdHash in the standard distribution for more detail.. We will also consider this more in the chapter on inheritance, when we see exactly how much typing you can avoid. |
![]() ![]() |
![]() ![]() |
![]() ![]() |
COMPUTING MCGRAW-HILL | Beta Books | Contact Us | Order Information | Online Catalog
HTML conversions by Mega Space.
This page updated on October 14, 1997 by Webmaster.
Computing McGraw-Hill is an imprint of the McGraw-Hill Professional Book Group.
Copyright ©1997 The McGraw-Hill Companies, Inc. All Rights Reserved.
Any use is subject to the rules stated in the
Terms of Use.