Intermediate Perl第十一章Building Larger Programs提供了代码复用的方法。
对于频繁使用的函数,可以将其定义放在一个单独的文件(*.pm)中,然后在需要调用函数的地方加载:
sub load_common_subroutines{
open my *more_fh, '<' 'Navigation.pm' or die "Navigation.pm: $!";
undef $/;
my $more_code = <$more_fn>;
close $more_fh;
eval $more_code;
die $@ if $@;
}
加载函数现将要加载的代码从pm文件中读入到$more_code中,然后用eval将$more_code中的代码加载到当前程序中,如果有语法错误,会设置$@变量。
于是,我们可以在需要加载函数的地方调用load_common_subroutines,之后使用pm中的函数。
但是上述方法还是有些麻烦,每次需要调用pm中的函数时,都需要实现load函数。do操作符可以简化这个操作:
do 'Navigation.pm';
die $@ if $@;
只需要将上述代码放在需要加载pm的代码中,就可以实现eval类似的功能。do操作符会将pm中的代码吸收到当前的程序中的作用域,所以被包含文件中的词法(my变量)和大多数指令(比如use strict)都不会泄漏到主程序中。
do操作符不像use和require会在模块目录下搜寻,需要提供pm文件的绝对路径或者相对路径给do。
考虑一种情况,如果Navigation.pm包含了另外一个pm文件DropAnchor.pm,那么使用Navigation.pm文件的代码就会将DropAnchor.pm中的函数定义两次,开启警告的情况下,就会收到Perl的提示信息。
require操作会记录所有已经包含的文件,保证每个文件只包含一次。一旦Perl成功处理包含的文件后,会忽略后续的相同的由require包含的文件(Perl使用%INC来保存已经加载的模块)。
require具有以下特点:
- 被包含文件中的任何错误都会导致程序die
- 文件中的最后一个表达式必须返回真值,以便require函数能够判断导入成功
但是,如果require的文件中定义了程序中名称相同的函数,编译器会首先编译程序中的函数,然后在运行时执行require操作,会重新定义同名的函数,产生函数重定义的警告。一个直观的解决方法就是在每个文件的标志符前都添加文件名的前缀,比如Navigation.pm中的标志符(变量和函数)都以navigation_开始。
上述方法可以解决命名空间的问题,但是明显不够优雅,假设一个文件名为RemoveGobbledygook.pm,代码中每次调用其中的函数都需要添加这一大串!
package很好的解决了这个问题,将其添加到文件的开始,Perl会在文件中的大部分标志符前添加Filename::,包含文件的程序即可以此方式调用同名的函数。
需要说明的是,主程序中的函数都属于package main,虽然Perl不需要显式定义main函数。所有的Perl文件都以package main开始,任何package指令在下一个包含package指令的大括号之前一直有效:
package Navigation;
{ # start scope block
package main; # now in package main
sub turn_toward_heading { #main::turn_toward_heading
...
}
} # end scope block
# back to package Navigation
sub turn_toward_port { # Navigation::turn_toward_port
...
}
词法变量(my变量)不会有当前包的前缀,因为包的变量通常是全局的:如果知道包的名字,就可以引用包内的变量;而词法变量通常供程序临时使用。但是在变量前添加包的前缀就会引用包内的变量:
package Navigation;
our @homeport = (21.283, -157.842); # package version
sub get_me_home {
my @homeport;
.. @homeport .. # refers to the lexical variable
.. @Navigation::homeport .. # refers to the package variable
}
.. @homeport .. # refers to the package variable
从v5.12以后,我们可以用package来修饰代码块:
package Navigation{
our @homeport = (21.283, -157.842); # package version
sub get_me_home {
my @homeport;
.. @homeport .. # refers to the lexical variable
.. @Navigation::homeport .. # refers to the package variable
}
.. @homeport .. # refers to the package variable
}
这种写法等同于:
{
package Navigation;
our @homeport = (21.283, -157.842); # package version
sub get_me_home {
my @homeport;
.. @homeport .. # refers to the lexical variable
.. @Navigation::homeport .. # refers to the package variable
}
.. @homeport .. # refers to the package variable
}
可以方便的在一个文件中定义多个包:
use v5.12;
package Navigation {
...
}
package DropAnchor {
...
}
需要注意的是,包的作用域同样会限制使用的词法变量的作用域。package还可以指明版本号package Navigation 0.01;
,可以快速设置$VERSION变量。