Sign In/My Account | View Cart  
advertisement


Listen Print

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

We've seen how useful a well crafted composite structure can be, but how could we build one for ourselves? The objects in the structure must all respond to the methods meant to walk the composite. They may return undef immediately, but they must exist. Further, the version of those methods in the composite objects (the ones which can have children), must take care to pass the message along to their children.

To make this concrete, consider a non-binary tree (as we have been all along). Suppose we want to know how many nodes are in the tree. We can ask the root to count_nodes. It should count itself and add that to the sum of count_nodes calls to each child. Nodes which are not composite (i.e. have no children) return one. Composite nodes, return one plus the sums from their children. The code follows.

There are four pieces of code: (1) A base class for tree nodes: Node.pm, (2) A class for nodes that could have children: Composite.pm, (3) A class for nodes that can't have children: Regular.pm, and (4) a driver to demonstrate that the system works: comp. I'll show these one at a time, in the order listed above.


    package Node;
    use strict; use warnings;

    sub count_nodes {
        my $self       = shift;
        my $class_name = ref $self;
        die "$class_name does not implement count_nodes\n";
    }

    1;

The only method here is count_nodes. This serves as an implementation requirement (also called an abstract method). Attempting to use a Node subclass which doesn't provide count_nodes results in a fatal run-time error. Every subclass should have an appropriate test to make sure this error never happens to users.


    package Regular;

    use Node;
    @ISA = qw(Node);

    use strict; use warnings;

    sub new {
        my $class = shift;
        my $name  = shift;
        return bless \$name, $class;
    }

    sub count_nodes {
        return 1;
    }

    1;

Regular nodes are blessed references to their names. They always count as a single node. (An unrelated note: it is sometimes convenient to turn on strict after the preamble of a package, here that let's me use @ISA without qualifying it.)


    package Composite;

    use Node;
    @ISA = qw(Node);

    use strict; use warnings;

    sub new {
        my $class = shift;
        my $name  = shift;
        my $self  = { name => $name, children => [] };
        return bless $self, $class;
    }

    sub add_child {
        my $self      = shift;
        my $new_child = shift;

        push @{$self->{children}}, $new_child;
        return $new_child;
    }

    sub count_nodes {
        my $self  = shift;
        my $count = 1;

        foreach my $child (@{$self->{children}}) {
            $count += $child->count_nodes();
        }
        return $count;
    }

    1;

This class is similar to Regular, but it needs a way to keep track of children. Since it also keeps its name, I used a hash for the object type. New children are just pushed onto a list. Counting includes one for the parent node, plus the total for each child. Since leaves of the tree also implement count_nodes, we can process all Node types together. This is the polymorphism advantage of objects and the heart of the Composite Pattern.


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

    use Composite;
    use Regular;

    my $root     = Composite->new("Root");

    my $eldest   = $root->add_child(Composite->new("Jim"));
    my $middle   = $root->add_child(Composite->new("Jane"));
                   $root->add_child(Regular->new("Bob"));
    my $youngest = $root->add_child(Composite->new("Joe"));

                   $eldest->add_child(Regular->new("JII"));
    my $kayla    = $eldest->add_child(Composite->new("Kayla"));
                   $kayla->add_child(Regular->new("Max"));

    my $count = $root->count_nodes();

    print "count: $count\n";

This contrived example manually builds a simple tree, then asks for a node count. The correct answer is 8.

Proxy

In GoF the proxy pattern example shows a way to delay loading expensive components until the user actually wants them. In the course of the example they show a genuine proxy. Proxies refer all requests to some other object. Think of it like an intermediary for the mob. You make your request to your local thug, as if he could do the thing himself. He passes that on to someone else you never meet who actually does the job. (Note to John Ashcroft: I am only imaging this process, having NO personal experience with it. Honest.)

Suppose an application could use several large files, but usually only needs one or two. Instead of reading all these files, I will delay loading the file until the caller wants to see it. The usual warning applies: this is contrived to explain the concept.

Here is the class that actually stores and prints the files:


    package File;
    use strict; use warnings;

    sub new {
        my $class = shift;
        my $file  = shift;
        open FILE, "$file" or die "Couldn't read $file: $!\n";
        my @data  = <FILE>;
        close FILE;
        return bless \@data, $class;
    }

    sub print_file {
        my $data = shift;
        print @$data;
    }

    sub DESTROY { }

    1;

When the File constructor is called, it reads the file into an array for later use, returning a blessed reference to the data to the caller. When asked to print, it sends the data to the currently selected output handle (usually standard out).

The DESTROY subroutine is called by Perl whenever a blessed reference is about to go out of scope. This allows us to perform clean-up which is guaranteed to happen. In this case, there is no necessary clean-up, but the approach I'm about to show for the proxy class ends up calling this method explicitly. That explicit call offends Perl so much that it complains to the screen. To avoid the warning, I included the stub.

There is nothing special about the File class shown above. The proxy follows.


    package FileProxy;
    use strict; use warnings;

    use File;

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

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

        unless (defined $self->{wrapped_object}) {
            $self->{wrapped_object} = File->new(@{$self->{params}});
        }
        $self->{wrapped_object}->$command(@_);
    }

    1;

The constructor for the proxy takes the things necessary to build an actual File object (namely the file name) and stores them as its params attribute. The other attribute will eventually hold the wrapped File object. The attributes are stored in hash, the hash's reference is blessed and returned to the caller.

Whenever Perl has no where else to go with a method call, it calls AUTOLOAD (if there is one). So, the AUTOLOAD in FileProxy handles all requests except new and DESTROY, which appear explicitly. AUTOLOAD is all caps to remind us that Perl calls it for us. While making this call, Perl sets the package global variable $AUTOLOAD to the name of the method the caller invoked. The regular expression strips off the package names from $AUTOLOAD, leaving only the method name.

Pages: 1, 2, 3

Next Pagearrow