这里有两个问题:
- 当作为独立执行和作为模块使用时,如何更改脚本的行为?
- 如何发现我刚刚编译的一段代码的包名?
问题 2 的一般答案是:您不需要,因为任何编译单元都可能包含任意数量的包。
无论如何,这里有三种可能的解决方案:
- 命名您的模块,以便您在加载它时已经知道名称。
- 让每个模块在一个中心集合点注册自己。
- 与 #1 类似,但添加了插件的自动发现功能。
最简单的解决方案是将所有 API 放在一个普通模块中,并将独立逻辑放在单独的脚本中:
/the/location/
Module/
A.pm
B.pm
a-standalone.pl
b-standalone.pl
每个独立的基本上看起来像
use Module::A;
Module::A->run();
如果另一个脚本想要重用该代码,它会
use lib "/the/location";
use Module::A;
...
如果加载发生在运行时,那么Module::Runtime
这里有帮助:
use Module::Runtime 'use_module';
use lib "/the/location";
my $mod_a = use_module('Module::A');
$mod_a->run();
将内容放入单独的文件中并不是绝对必要的a-standalone.pl
,Module/A.pm
尽管这样更清楚。如果你想有条件地运行一个模块中的代码,只有当它被用作脚本时,你可以利用这个unless(caller)
技巧。
当然,所有这些都是骗人的:这里我们根据模块名称确定文件名,而不是反过来——正如我已经提到的,我们不能这样做。
我们可以做的是让每个模块在某个预定义的位置注册自己,例如通过
Rendezvous::Point->register(__FILE__ => __PACKAGE__);
当然,独立版本必须屏蔽没有 的可能性Rendezvous::Point
,因此:
if (my $register = Rendezvous::Point->can("register")) {
$register->(__FILE__ => __PACKAGE__);
}
呃,这很愚蠢,违反了DRY。所以让我们创建一个Rendezvous::Point
模块来处理这个问题:
在/the/location/Rendezvous/Point.pm
:
package Rendezvous::Point;
use strict; use warnings;
my %modules_by_filename;
sub get {
my ($class, $name) = @_;
$modules_by_filename{$name};
}
sub register {
my ($file, $package) = @_;
$modules_by_filename{$file} = $package;
}
sub import {
my ($class) = @_;
$class->register(caller());
}
现在,use Rendezvous::Point;
注册调用包,并且可以通过绝对路径检索模块名称。
想要使用各种模块的脚本现在执行以下操作:
use "/the/location";
use Rendezvous::Point (); # avoid registering ourself
my $prefix = "/the/location";
for my $filename (map "$prefix/$_", qw(Module/A.pm Module/B.pm)) {
require $filename;
my $module = Rendezvous::Point->get($filename)
// die "$filename didn't register itself at the Rendezvous::Point";
$module->run();
}
然后是功能齐全的插件系统,例如Module::Pluggable
. 该系统通过查看 Perl 模块可能驻留的所有路径来工作,并在它们具有特定前缀时加载它们。一个解决方案看起来像:
/the/location/
MyClass.pm
MyClass/
Plugin/
A.pm
B.pm
a-standalone.pl
b-standalone.pl
一切都和第一个解决方案一样:独立脚本看起来像
use lib "/the/location/";
use MyClass::Plugin::A;
MyClass::Plugin::A->run;
但MyClass.pm
看起来像:
package MyClass;
use Module::Pluggable require => 1; # we can now query plugins like MyClass->plugins
sub run {
# Woo, magic! Works with inner packages as well!
for my $plugin (MyClass->plugins) {
$plugin->run();
}
}
当然,这仍然需要特定的命名方案,但它会自动发现可能的插件。