Can I access a static method in a dynamically spec

2019-04-05 11:17发布

问题:

Is it possible to dynamically specify a class in Perl and access a static method in that class? This does not work, but illustrates what I'd like to do:

    use Test::Class1;  
    my $class = 'Test::Class1';  
    $class::static_method();    

I know I can do this:

    $class->static_method();  

and ignore the class name passed to static_method, but I wonder if there's a better way.

回答1:

Yup! The way to do it with strictures is to use can.

package Foo::Bar;
use strict;
use warnings;

sub baz
{
   return "Passed in '@_' and ran baz!";
}

package main;
use strict;
use warnings;

my $class = 'Foo::Bar';

if (my $method = $class->can('baz'))
{
   print "yup it can, and it ";
   print $method->();
}
else
{
   print "No it can't!";
}

can returns a reference to the method, undef / false. You then just have to call the method with the dereferene syntax.

It gives:

    > perl foobar.pl
    yup it can, and it Passed in '' and ran baz!


回答2:

As always with Perl, there is more than one way to do it.

use strict;
use warnings;
{
  package Test::Class;
  sub static_method{ print join(' ', @_), "\n" }
}
  • You can use the special %:: variable to access the symbol table.

    my $class = 'Test::Class';
    my @depth = split '::', $class;
    
    my $ref = \%::;
    $ref = $glob->{$_.'::'} for @depth; # $::{'Test::'}{'Class::'}
    
    $code = $glob->{'static_method'};
    $code->('Hello','World');
    
  • You could just simply use a symbolic reference;

    no strict 'refs';
    my $code = &{"${class}::static_method"};
    # or
    my $code = *{"${class}::static_method"}{CODE};
    $code->('Hello','World');
    
  • You could also use a string eval.

    eval "${class}::static_method('Hello','World')";
    
  • The simplest in this case, would be to use UNIVERSAL::can.

    $code = $class->can('static_method');
    $code->('Hello','World');
    


回答3:

I am unaware of a particularly nice way of doing this, but there are some less nice ways, such as this program:

#!/usr/bin/perl -w

use strict;

package Test::Class1;

sub static_method {
  print join(", ", @_) . "\n";
}

package main;

my $class = "Test::Class1";

{
  no strict "refs";
  &{${class}. "::static_method"}(1, 2, 3);
}

I have included a $class variable, as that was how you asked the question, and it illustrates how the class name can be chosen at runtime, but if you know the class beforehand, you could just as easily call &{"Test::Class1::static_method"}(1, 2, 3);

Note that you have to switch off strict "refs" if you have it on.



回答4:

You can use string eval:

#!/usr/bin/perl

use strict; use warnings;

package Test::Class1;

sub static_method {
  print join(", ", @_) . "\n";
}

package main;

my $class = 'Test::Class1';
my $static_method = 'static_method';

my $subref = eval q{ \&{ "${class}::${static_method}" } };
$subref->(1, 2, 3);

Output:

C:\Temp> z
1, 2, 3

Benchmarks:

#!/usr/bin/perl

use strict; use warnings;

package Test::Class1;

sub static_method { "@_" }

package main;

use strict; use warnings;
use Benchmark qw( cmpthese );

my $class = 'Test::Class1';
my $static_method = 'static_method';

cmpthese -1, {
    'can' => sub { my $r = $class->can($static_method); $r->(1, 2, 3) },
    'eval' => sub {
        my $r = eval q/ \&{ "${class}::${static_method}" } /;
        $r->(1, 2, 3);
    },
    'nostrict' => sub {
        no strict "refs";
        my $r = \&{ "${class}::static_method" };
        $r->(1, 2, 3);
    }
};

Output:

             Rate     eval      can nostrict
eval      12775/s       --     -94%     -95%
can      206355/s    1515%       --     -15%
nostrict 241889/s    1793%      17%       --


回答5:

There are three main ways to call a static function:

  • $object->static_method()
  • Classname->static_method()
  • Classname::static_method()

You could define your function like this:

# callable as $object->static_method() or Classname->static_method()
sub static_method
{
    my $class = shift;    # ignore; not needed
    # ...
}

or like this, which works in all three calling scenarios, and doesn't incur any overhead on the caller's side like Robert P's solution does:

use UNIVERSAL qw(isa);

sub static_method
{
    my $class = shift if $_[0] and isa($_[0], __PACKAGE__);
    # ...
}


标签: perl oop