BrowserUk has asked for the wisdom of the Perl Monks concerning the following question:

I was looking at how to subclass Net::SMTP::Server::Client2 and found that the usual method of overriding methods doesn't work for most of the interesting ones because they are invoked internally through a lexical dispatch table.

(See the &{$_cmds{$cmd}}($self, \@args) line of get_message(). source code)

I can see several ways of doing this, but they all require modifications to the superclass. I did it this way. How would you do it?


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
"Too many [] have been sedated by an oppressive environment of political correctness and risk aversion."

Replies are listed 'Best First'.
Re: Subclassing a class that uses an internal dispatch table
by broquaint (Abbot) on Oct 30, 2007 at 13:16 UTC
    I'd probably make use of PadWalker like so:
    use PadWalker 'peek_sub'; my $sub = \&Net::SMTP::Server::Client2::get_message; my $cmds = peek_sub($sub)->{'%_cmds'}; $cmds->{$_} = __PACKAGE__->can($_) || $cmds->{$_} for keys %$cmds;
    Of course that won't support multiple subclasses, but it will work for the simple case of a single subclass. It's "a hack for a hack" if you will.
    HTH

    _________
    broquaint

Re: Subclassing a class that uses an internal dispatch table
by dragonchild (Archbishop) on Oct 30, 2007 at 13:01 UTC
    Email the author and ask why? That seems like an awful lot of work for what was probably a misapprehension on the author's part.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      That's an interesting take on the problem. Using a dispatch table is the archetypal "best practice" for implementing externally derived, command driven dispatching, so I guess the question then becomes:

      How would you implement a dispatch table within a class so that the dispatched code can be overridden?

      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        We already have one. It's called the Perl dispatcher.
        # Sanitize $cmd here if you want. Maybe you go against a list of a +cceptable # commands that's accessible via $self->add_command( 'cmd1' ); my $method = $self->can( $cmd ); $self->$method( @args ) if $method;
        Is there something I'm missing? This is very similar to what SQL::Parser does and it's pretty successful.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Subclassing a class that uses an internal dispatch table
by TOD (Friar) on Oct 30, 2007 at 03:32 UTC
    package Net::SMTP::Server::Client2::MySubClass; use strict; use vars qw/@ISA/; use Net::SMTP::Server::Client2; @isa = qw/Net::SMTP::Server::Client2/; my %private_commands = ( HELO => \&my_hello, [...] ); sub new { my ($class, $sock) = @_; my $self = $class->SUPER::new($sock); bless $self, __PACKAGE__; } [...] sub get_message { my ($self, $cmd, @args) = @_; return $self->SUPER::get_message($cmd, @args) unless exists $privat +e_commands{$cmd}; # from here on mimic the super class's method, but with # calls to your private, "overridden" methods. } sub my_hello { # perform some individual stuff. }
    --------------------------------
    masses are the opiate for religion.

      I think you may have misread the source code of Client2. The variables $cmd, @args are not input parameters to get_message() but rather just local variable declarations:

      sub get_message { my $self = shift; my($cmd, @args); ...

      That doesn't negate your method, but does require you to modify the dispatching to check your dispatch table before dispatching any non-overridden methods to the superclass at the bottom of the read loop rather than at the top of the sub.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        yes, you're right. both $cmd and @args will be received through the socket. hmm... what about this one:
        #!/usr/bin/perl -w { package Net::SMTP::Server::Client2::Subclass; use strict; use vars qw/@ISA/; use Net::SMTP::Server::Client2; @ISA = qw/Net::SMTP::Server::Client2/; my %_pcmds = ( HELO => \do { print STDERR "hello\n" } ); our $eval_command = sub { my ($self, $cmd, @args) = @_; my $hash = (__PACKAGE__ =~ /Subclass$/) ? '$_pcmds' : '$ +_cmds'; $cmd = "&{$hash"."{$cmd}}(\$self, \\\@args)"; eval $cmd or return(defined($self->{MSG})); }; &Net::SMTP::Server::Client2::eval_command = \$eval_command; sub new { my ($class, $sock) = @_; my $self = $class->SUPER::new($sock); bless $self, __PACKAGE__; } sub get_message { my $self = shift; my ($cmd, @args); # [...} # do everything the super class does, up to this point: if (exists $_pcmds{$cmd}) { $eval_command->($self, $cmd, @args); } else { $self->SUPER::eval_command($cmd, @args); } } 1; }
        i've never done that before, but at least the compiler doesn't complain.
        --------------------------------
        masses are the opiate for religion.