Perl: Как импортировать подпрограммы из базового класса?

У меня есть базовый класс с именем Foo::Base, мне нужно наследовать его методы, такие как 'new', и импортировать имена некоторых подпрограмм в области видимости:

package Foo::Base;

sub new { ... }

sub import {
    no strict 'refs';

    my $caller = caller;

    *{"${caller}::my_sub"} = sub { 1 };
}

1;

Итак, мне нужно использовать этот базовый класс во втором классе, Foo:: Child:

use base 'Foo::Base';

... и работает для наследования, но не импортирует my_sub в область видимости. Я могу добавить строку

use Foo::Base;

для этого и это помогает, но я не хочу писать что-то вроде этого:

use base 'Foo::Base';
use Foo::Base;

Это выглядит странно... Есть какие-либо предложения для этой проблемы?

3 ответа

Есть две причины, по которым можно хотеть делать то, что ты делаешь, обе они плохие.

Во-первых, вы пытаетесь импортировать методы из родительского класса... по какой-то причине. Может быть, вы неправильно понимаете, как работает ОО. Вам не нужно это делать. Просто вызовите унаследованные методы как методы, и если эти методы не делают что-то дурацкое, это будет работать нормально.

Скорее всего, это многофункциональный модуль, где некоторые из них являются методами, а некоторые - импортированными функциями. И для этого вы можете сделать...

use base 'Foo::Base';
use Foo::Base;

И вы правильно заметили, что это выглядит странно... потому что это странно. Класс, который также экспортирует, смешивает идиомы, и это приведет к странным моделям использования.

Лучше всего переделать класс, чтобы вместо экспорта функций разделить функции на их собственные модули или создать методы класса. Если функции на самом деле не имеют ничего общего с классом, то лучше всего их выделить. Если они действительно относятся к классу, то сделайте их методами класса.

use base 'Foo::Base';

Foo::Base->some_function_that_used_to_be_exported;

Это устраняет несоответствие интерфейса, и в качестве бонуса подклассы могут переопределять поведение метода класса, как и любой другой метод.

package Bar;

use base 'Foo::Base';

# override
sub some_function_that_used_to_be_exported {
    my($class, @args) = @_;

    ...do something extra maybe...

    $class->SUPER::some_function_that_used_to_be_exported(@args);

    ...and maybe something else...
}

Если у вас нет контроля над базовым классом, вы все равно можете сделать интерфейс разумным, написав подкласс, который превращает экспортируемые функции в методы.

package SaneFoo;

use base 'Foo::Base';

# For each function exported by Foo::Base, create a wrapper class
# method which throws away the first argument (the class name) and
# calls the function.
for my $name (@Foo::Base::EXPORT, @Foo::Base::EXPORT_OK) {
    my $function = Foo::Base->can($name);
    *{$name} = sub {
        my $class = shift;
        return $function->(@_);
    };
}

Когда ты пишешь use base Вы используете средства base модуль. И вы передаете ему параметр модуля, который вы хотите использовать в качестве базового класса.

В отношениях OO IS-A импорт не требуется. Вы вызываете методы с OO-шаблоном: $object_or_class->method_name( @args ), Иногда это означает, что вам все равно, кто этот инвокант, например:

sub inherited_util {
    my ( undef, @args ) = @_;
    ... 
}

или же

sub inherited2 { 
    shift;
    ...
}

Однако, если вы хотите использовать утилиты, определенные в базовом модуле, и наследовать от поведения класса, определенного в этом модуле, то это именно то, что указывают два оператора использования.

Тем не менее, если у вас есть два разных типа поведения, которые вы хотите использовать в модулях, вероятно, лучше разбить вещи типа утилит на их собственный модуль и использовать его из обоих модулей. В любом случае, явное поведение часто лучше, чем неявное.

Тем не менее, я использовал этот шаблон раньше:

sub import { 
    shift;
    my ( $inherit_flag ) = @_;
    my $inherit 
        = lc( $inherit_flag ) ne 'inherit' ? 0
        : shift() && !!shift()             ? 1
        :                                    0
        ;
    if ( $inherit ) { 
        no strict 'refs';
        push @{caller().'::ISA'}, __PACKAGE__;
        ...
    }
    ...
}

таким образом, я делаю один вызов с явным набором использования.

use UtilityParent inherit => 1, qw<normal args>;

Чтобы прояснить немного вопрос "а почему не работает импорт в Foo::Child?", Вот программа, которая разобьет, что на самом деле происходит:

use Foo::Child;
print my_sub(); # Really? Yes, really!
print Foo::Child::my_sub()," done\n";

и добавил это в подпрограмму Foo::Base::import():

print "Caller is $caller\n";

Когда вы запускаете это, вы видите это как вывод:

Caller is main
1
Undefined subroutine &Foo::Child::my_sub called at foo_user.pl line 4.

Мы видим отчеты Foo:: Base, которые сообщают нам, кто звонил: это основная программа! Мы видим '1', который доказывает, что yes, main::my_sub теперь существует, а затем происходит сбой, потому что импорт пошел в неправильное пространство имен.

Почему это? Потому что все процессы импорта обрабатываются основной программой. Импорт Foo:: Base не вызывается ничем в Foo::Child; он вызывается основной программой в процессе загрузки модулей. Если вы действительно хотите импортировать подпрограммы в противоположность методам в Foo::Child, вам нужно явно сделать это в самом Foo::Child. 'Use Foo::Base' сделает это, так как это заставляет импорт выполняться с Foo::Child в качестве вызывающей стороны; если вы настаиваете на импорте, но двойное использование приводит к слишком сильным последствиям, вы можете вызвать Foo::Base::import() сразу после 'use base'. Это именно то, что делает двойное использование в любом случае.

Тем не менее, мне больше нравятся методы класса Шверна, и я рекомендую эту альтернативу.

Другие вопросы по тегам