Могу ли я получить доступ к статическому методу в динамически заданном классе в Perl?

Можно ли динамически указать класс в Perl и получить доступ к статическому методу в этом классе? Это не работает, но иллюстрирует, что я хотел бы сделать:

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

Я знаю, что могу это сделать:

    $class->static_method();  

и игнорировать имя класса, переданное static_method, но мне интересно, есть ли лучший способ.

Ответ 1

Да! Способ сделать это с помощью ограничений - использовать 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 возвращает ссылку на метод undef/false. Затем вам просто нужно вызвать метод с синтаксисом dereferene.

Он дает:

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

Ответ 2

Как всегда с Perl, существует несколько способов сделать это.

use strict;
use warnings;
{
  package Test::Class;
  sub static_method{ print join(' ', @_), "\n" }
}
  • Вы можете использовать специальную переменную %:: для доступа к таблице символов.

    my $class = 'Test::Class';
    my @depth = split '::', $class;
    
    my $ref = \%::;
    $ref = $glob->{$_.'::'} for @depth; # $::{'Test::'}{'Class::'}
    
    $code = $glob->{'static_method'};
    $code->('Hello','World');
    
  • Вы можете просто использовать символическую ссылку ;

    no strict 'refs';
    my $code = &{"${class}::static_method"};
    # or
    my $code = *{"${class}::static_method"}{CODE};
    $code->('Hello','World');
    
  • Вы также можете использовать строку eval.

    eval "${class}::static_method('Hello','World')";
    
  • Простейшим в этом случае было бы использовать UNIVERSAL::can.

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

Ответ 3

Я не знаю об особенно хорошем способе этого, но есть несколько менее приятных способов, таких как эта программа:

#!/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);
}

Я включил переменную $class, так как вы задали этот вопрос, и это иллюстрирует, как имя класса можно выбрать во время выполнения, но если вы знаете класс заранее, вы можете так же легко вызвать &{"Test::Class1::static_method"}(1, 2, 3);

Обратите внимание, что вам нужно отключить strict "refs", если вы его включили.

Ответ 4

Вы можете использовать строку 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);

Вывод:

C:\Temp> z
1, 2, 3

Ориентиры:

#!/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);
    }
};

Вывод:

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

Ответ 5

Существует три основных способа вызова статической функции:

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

Вы можете определить свою функцию следующим образом:

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

или как это, что работает во всех трех сценариях вызова и не несет никаких накладных расходов на стороне вызывающего абонента, как это делает решение Robert P:

use UNIVERSAL qw(isa);

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