[Maine-pm] calling callbacks as object methods via hash lookup
maine-pm at mail.pm.org
maine-pm at mail.pm.org
Mon Jul 12 14:01:26 CDT 2004
Joe,
Thanks for the suggestions. I appreciate your review of the code and
have enclosed some responses which may be of interest, depending on
your workload and how easily amused you are by my awkward OO Perl code.
At the very least, it has helped me to attempt to explain it.
My overall objective was to write a logging module that would take a
simple call like $my_log->log($var) and then dispatch a series of
customizable callbacks from MyClass::log(). For example, _before()
could output a heading, _each() could deal with the arguments
themselves and _after() could follow up with a newline.
I used the hash as a convenience for registering the callbacks, though
in the production version they are only accessible through get/set
methods as you suggested, or in the constructor. So, for example, if
you said my $log = Log->new('before' => \&my_before_sub), it would
override the default. Inheritance is handled by calling a _prototype()
method inside the constructor which fetches the default callbacks from
whatever package the object was blessed into. (This is adapted from
Conway's book).
sub new {
my ($invocant, %args) = @_;
my $obj = ref($invocant); # is invocant an object?
my $class = $obj || $invocant;
my $self = bless {}, $class;
for my $prop ($self->_prototype()) {
my ($arg) = ($prop =~ /^_(.*)/);
if (exists $args{$arg}) {
$self->{$prop} = $args{$arg};
}
elsif ($obj) {
$self->{$prop} = $obj->{$prop};
}
else {
$self->{$prop} = $self->_prototype($prop);
}
}
return $self;
}
sub _prototype {
my $self = shift;
my %defaults = (
'_before_all' => \&_before_all,
'_before_each' => \&_before_each,
'_each' => \&_each,
'_after_each' => \&_after_each,
'_after_all' => \&_after_all,
'_log_tag' => \&_log_tag,
'_scalar' => \&_scalar,
'_array' => \&_array,
'_hash' => \&_hash,
'_code' => \&_code,
'_glob' => \&_glob,
'_ref' => \&_ref,
'_lvalue' => \&_lvalue,
'_io::handle' => \&_io_handle,
'_object' => \&_object,
'_default' => \&_default,
'_new_line' => "\n",
'_indent' => "\t",
'_line_char' => "^",
'_rule_length' => 80,
'_verbosity' => 3
);
return keys %defaults if wantarray;
my $prop = shift;
if (exists($defaults{$prop})) {
return $defaults{$prop};
}
}
The convenience factor I was looking for would come into play as all
these callbacks were calling each other at the upper reaches of the
call stack. Here's an example. If an arrayref is passed in to the log
method, it is recursively printed by calling '_each()' on each value.
sub _array {
my ($self, $ref, $indent, $v, $type, $address, $class) = @_;
return unless ref($self); # object method only
my $string;
# OBJECT HEADING
$string .= $self->${\$self->{'_object'}}($ref, $indent, $v, $type,
$address, $class) if $class;
# PREFIX
if ($v) {
$string .= $self->{'_new_line'} . $self->{'_indent'} x $indent . "[";
$string .= " # $type at $address" if $v >= 5;
$indent++;
}
# FIX
for (@{$ref}) {
$string .= $self->${\$self->{'_each'}}(\$_, $indent);
}
# POSTFIX
if ($v) {
$string .= $self->{'_new_line'} . $self->{'_indent'} x --$indent .
"]";
$string .= $indent? "," : ";";
}
return $string;
}
I like the idea of using a dispatcher method. Although it adds an
additional call to the stack it still supports the convenience of
invoking the callbacks with a single expression, as long as the name of
the intended callback is passed in as an argument (which would then be
used as a hash key). Also it would allow centralized error handling for
misspellings and bad coderefs.
Bogart
More information about the Maine-pm
mailing list