如上所述,有许多模块可以执行此操作,其中一些已命名。一个好的做法是为功能接口编写一个单独的模块,uses 类并导出它的(选择)函数。
但如果有特殊需要,很有可能将两个接口放在一个包中,并具有相同的方法/函数名称。有关以下基本示例无法处理的一个非常特定且罕见的用例,以及如何解决它,请参阅最后部分。
这是一个具有两个接口的基本包
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");
输出
作为类方法调用:
f1 中的 args:重复,称为类方法
作为方法调用:
f1中的args:Duplicious=HASH(0x21b1b48), HASH(0x21a8738),被称为方法
作为函数调用:
f1 中的 args:HASH(0x21a8720),作为函数调用
f1 中的 args:Duplicious=HASH(0x218ba68), HASH(0x21a8720),作为函数调用
函数调用分派给方法,因此有两行(注意参数)。
我发现在定义类的模块中使用Exporter 原则上很尴尬(但我不知道这样做有任何实际问题);它会导致一个潜在的混乱界面。这本身就是分离接口的一个很好的理由,以便功能性的必须加载特定的模块。
还有一个细节需要注意。方法调用
($obj_cache || __PACKAGE__->new)->f1(...)
使用缓存的$obj_cache(如果这个子已经被调用)来进行调用。因此,对象的状态被保留,在之前对f1 的调用中可能已经或没有被操纵。
这在用于非面向对象的上下文中的调用中相当重要,应该仔细研究。如果有问题,只需删除该缓存或将其扩展为完整的 if 语句,其中可以根据需要重置状态。
这两种用途绝对不能在同一个程序中混用。
要使用派生类进行测试,我使用最小的
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");
额外的输出是
作为派生类的方法调用
f1中的args:NextDupl=HASH(0x11ac720),作为派生类的方法调用
通过派生类从父级提取的coderef调用
f1 中的 args:NextDupl,由派生类通过父级的 coderef 调用
这包含使用UNIVERSAL::can 的测试,因为它出现在评论中。
在 cmets 中提出并讨论了一个特定的限制(我知道)。
假设我们编写了一个方法,它接受一个对象(或一个类名)作为它的第一个参数,所以被调用为->func($obj);进一步——这很重要——这个方法允许任何类,因为它的工作方式不关心它有什么类。这将是非常特殊的,但这是可能的,并且会引发以下问题。
这个方法对应的函数调用会是func($obj),而当$obj恰好在这个类的层次结构中会导致方法调用->func(),不正确。
在决定是否
它被称为函数或方法,因为它所做的只是查看第一个参数。如果它是我们自己的层次结构中的对象/类,它会确定这是对该对象的方法调用(或类方法调用),在这种特殊情况下是错误的。
模块的作者有两种简单的方法,可能还有另一种方法来解决这个问题
这一切都是完全合理的。在一个行使其定义特征、拥有和使用数据(“属性”)的类中,可能会有无法转换为函数调用的方法。这是因为单个程序应该只使用 one 接口,并且函数没有状态,因此依赖它的方法不会运行。 (为此使用缓存对象非常危险。)
因此,人们总是必须仔细决定界面,并进行选择。
感谢 Grinnz 提供 cmets。
请注意,“函数式编程”有一个完全不同的范式,而标题有点不清楚。所有这些都是关于过程方法中的功能接口。