Sign In/My Account | View Cart  
advertisement


Listen Print

Perl Design Patterns, Part 3
by Phil Crow | Pages: 1, 2, 3

If the object is not yet defined, AUTOLOAD calls File->new passing it the arguments stored during construction. After that, the object is defined, so AUTOLOAD calls the requested method on the wrapped object. The beauty of this mechanism is that the FileProxy class only knows that the constructor is called new. It does not need to change as changes to File.pm are made. Any errors, such as no such method, will be fatal as usual.

To use this proxied scheme we might employ a caller like this:


    #!/usr/bin/perl
    use strict; use warnings;

    use FileProxy;

    my $file1 = FileProxy->new("art1");
    my $file2 = FileProxy->new("art2");

    $file1->print_file();
    $file1->print_file();
    $file2->print_file();

With a couple of changes we could use this for any class. Here's the new generic version:


    package DelayLoad;
    use strict; use warnings;

    our %proxied_classes;

    sub import {
        shift;  # discard class name

        %proxied_classes = @_;

        foreach my $class (keys %proxied_classes) {
            require "$class.pm";
        }
    }

    sub new {
        my $class = shift;
        my $self  = {
            type           => shift,
            constructor    => shift,
            params         => \@_,
            wrapped_object => undef,
        };
        return bless $self, $class;
    }

    sub AUTOLOAD {
        my $self    = shift;
        my $command = our $AUTOLOAD;
        $command    =~ s/.*://;

        if ($proxied_classes{$command}) {
            return $self->new($command, $proxied_classes{$command}, @_);
        }
        else {
            unless (defined $self->{wrapped_object}) {
                my $proxied_class       = $self->{type};
                my $constructor         = $self->{constructor};
                $self->{wrapped_object} = $proxied_class
                                        ->$constructor(@{$self->{params}});
            }
            $self->{wrapped_object}->$command(@_);
        }
    }

    1;

The first change is cosmetic: the name now reflects the nature of the proxy. Other changes include a new method: import. Even though its name is lower case, Perl calls it whenever the caller says use DelayLoad (see below). It does two things. First, it stores the name of each proxied class in the %proxied_classes package global. Second, it requires each module. require is like use, but it happens at run time instead of compile time. (use also imports symbols, but then your object oriented module shouldn't be exporting anything anyway.)

The constructor now stores a bit more information. In addition to saving room for the wrapped object and storing the params, it also records the name of the class and of that class's constructor. These will be used in AUTOLOAD.

The only other changes are in the AUTOLOAD method. There are two changes. The easiest one is to look up the class and constructor names in the DelayLoad object instead of just calling File->new.

The other change is used during construction. My explanation of it will make more sense, if you see the new caller first.

The new version requires a couple of changes to the caller. One change is on the use line which becomes:


    use DelayLoad "File" => "new";

This uses DelayLoad, tells it we want to be able to delay loads for File objects, and that File's constructor is called new.

The other change is in how we construct the delayed object:


    my $file1 = DelayLoad->File("art1");
    my $file2 = DelayLoad->File("art2");

This explains the unexplained piece in AUTOLOAD above. When the user calls the File method, AUTOLOAD notices that this ``method'' is really the name of a delay loaded class. When the if in AUTOLOAD is true (i.e. the method is really a key in %proxied_classes), the caller is given a new DelayLoad object primed for later use. When the if fails, DelayLoad works like FileLoad: it constructs the object, if needed and calls the requested method.

The fundamental point of this example is that Perl allows us to implement proxies without knowing very much about the underlying class. In this case, import receives the necessary information from the caller, AUTOLOAD takes care of the rest. Making the caller work is not always a good idea. Here it makes sense. If she knows she wants to delay loading objects until they are really needed, she must at least know the API for those objects. In the API is the name of the constructor, which she mentions in the use statement so Perl can pass to DelayLoad::import for her.

Keep in mind that AUTOLOAD is not designed for this sort of work. Its real purpose in life is to load subroutines on demand for the current package. It can't do that here, since changing the subroutines affects all instances of a class. Here we are AUTOLOADing data, not routines. By suitably adjusting import and AUTOLOAD, you can make the proxy do many other things.

Summary

In this article, I have finally shown object oriented patterns. We saw how to implement a Factory so our callers can choose their favorite driver, how to build composite structures and routines that traverse them (without explicit first_child and next pointers that would be needed in languages without quality built-in lists), and how to stand as a proxy between a caller and a class with import and AUTOLOAD.

Author's Note

This is the final article in this series, but look for a book, Design Patterns in Perl from Apress at your favorite bookseller in the near future.