Как уже говорилось, есть много модулей, которые делают это, и некоторые из них были названы.Хорошей практикой является написание отдельного модуля для функционального интерфейса, который use
присваивает классу и экспортирует его (выборочные) функции как таковые.
Но возможно иметь оба интерфейса в одном пакете с одинаковыми именами методов / функций, если в этом есть особая необходимость.См. Раздел в конце для одного очень конкретного и редкого варианта использования, который не будет обработан в следующем базовом примере, и как его решить.
Вот базовый пакет, которыйимеет оба интерфейса
package Duplicious; # having interfaces to two paradigms may be confusing
use warnings;
use strict;
use feature 'say';
use Scalar::Util qw(blessed);
use Exporter qw(import);
our @EXPORT_OK = qw(f1);
my $obj_cache; # so repeated function calls don't run constructor
sub new {
my ($class, %args) = @_;
return bless { }, $class;
}
sub f1 {
say "\targs in f1: ", join ', ', @_; # see how we are called
my $self = shift;
# Functional interface
# (first argument not object or class name in this or derived class)
if ( not ( (blessed($self) and $self->isa(__PACKAGE__))
or (not ref $self and $self->isa(__PACKAGE__)) ) )
{
return ($obj_cache || __PACKAGE__->new)->f1($self, @_);
}
# Now method definition goes
# ...
return 23;
}
1;
Абонент
use warnings; # DEMO only --
use strict; # Please don't mix uses in the same program
use feature 'say';
use Duplicious qw(f1);
my $obj = Duplicious->new;
say "Call as class method: ";
Duplicious->f1("called as class method");
say "Call as method:";
my $ret_meth = $obj->f1({}, "called as method");
say "\nCall as function:";
my $ret_func = f1({}, "called as function");
В принципе неудобно использовать Exporter
в модуле, который определяет класс (но я не знаю ни одного фактическогопроблемы с этим);это приводит к потенциально запутанному интерфейсу.Это само по себе является хорошей причиной для разделения интерфейсов, так что функциональный должен загрузить определенный модуль.
Есть также одна деталь, которая требует внимания.Вызов метода
($obj_cache || __PACKAGE__->new)->f1(...)
использует кешированный $obj_cache
(если этот подпрограмма уже был вызван) для выполнения вызова.Таким образом, сохраняется состояние объекта, которое могло или не было изменено в предыдущих вызовах f1
.
Это довольно нетривиально в вызове, предназначенном для использования в не объектно-ориентированном контексте, и его следует тщательно изучить.Если есть проблемы с ним, либо отбросьте это кэширование, либо разверните его в полный оператор if
, где состояние может быть сброшено при необходимости.
Эти два использования должны абсолютно не смешиваться в одной и той же программе.
Вывод
Call as class method:
args in f1: Duplicious, called as class method
Call as method:
args in f1: Duplicious=HASH(0x21b1b48), HASH(0x21a8738), called as method
Call as function:
args in f1: HASH(0x21a8720), called as function
args in f1: Duplicious=HASH(0x218ba68), HASH(0x21a8720), called as function
Вызов функции отправляется методу, таким образомдве строки (обратите внимание на аргументы).
Для тестирования с производным классом я использую минимальный
package NextDupl;
use warnings;
use strict;
use feature 'say';
use parent 'Duplicious';
1;
и добавляю к основной программе выше следующее
# Test with a subclass (derived, inherited class)
my $inh = NextDupl->new;
say "\nCall as method of derived class";
$inh->f1("called as method of derived class");
# Retrieve with UNIVERSAL::can() from parent to use by subclass
my $rc_orig = Duplicious->can('f1');
say "\nCall via coderef pulled from parent, by derived class";
NextDupl->$rc_orig("called via coderef of parent by derived class");
Дополнительный вывод:
Call as method of derived class
args in f1: NextDupl=HASH(0x11ac720), called as method of derived class
Call via coderef pulled from parent, by derived class
args in f1: NextDupl, called via coderef of parent by derived clas
Это включает в себя тест с использованием UNIVERSAL::can
, как он появился в комментарии.
Существует одно конкретное ограничение (которое яосведомлен о), поднят и обсужден в комментариях.
Представьте, что мы пишем метод, который принимает объект (или имя класса) в качестве первого аргумента, чтобы его вызывали как ->func($obj)
;далее - и это важно - этот метод допускает любой класс, так как он работает таким образом, что ему все равно, какой у него класс.Это было бы очень конкретно, но это возможно, и это поднимает следующую проблему.
Вызов функции, соответствующий этому методу, будет func($obj)
, и когда $obj
окажется в иерархии этого класса, что приведет к неправильному вызову метода ->func()
, .
Нет способа устранить неоднозначность в коде, который решает, вызывается ли он как функция или как метод, поскольку все, что он делает, это смотрит на первый аргумент.Если это объект / класс в нашей собственной иерархии, он решает, что это был вызов метода для этого объекта (или вызов метода класса), и в данном конкретном случае это неправильно.
Есть два простых способа:и, возможно, еще один, для автора модуля, чтобы решить это
Не предоставлять функциональный интерфейс для этого весьма специфического метода
Дайте емуотдельное (явно связанное) имя
Условие if
, которое определяет способ вызова, проверяя первый аргумент, является постоянным, но все же записывается для каждого метода, имеющего этот интерфейс.Поэтому в этом методе проверьте еще один аргумент: если первый - это объект / класс этого класса и , то следующий - (любой) объект / класс, тогда это вызов метода. Это не работает, если второй аргумент является необязательным.
Все это совершенно разумно.В классе, который использует свою определяющую черту - иметь и использовать данные («атрибуты»), скорее всего, будут методы, которые нельзя преобразовать в вызовы функций.Это связано с тем, что одна программа должна использовать только один интерфейс, а у функций нет состояния, поэтому методы, полагающиеся на это, не будут работать.(Использование кэшированного объекта для этого крайне коварно.)
Так что всегда нужно будет тщательно определиться с интерфейсом и выбирать и выбирать.
Благодаря Grinnz для комментариев.
Обратите внимание, что существует совершенно другая парадигма «функционального программирования», и название оставляет это немного неясным.Все это о функциональном интерфейсе в процедурном подходе.