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'. Это именно то, что делает двойное использование в любом случае.
Тем не менее, мне больше нравятся методы класса Шверна, и я рекомендую эту альтернативу.