第九章 管理真实的程序(六) -UNIVERSAL包
UNIVERSAL包
Perl内部的UNIVERSAL包是其他所有包的祖先---以面向对象的视角来看那就是终极父类。UNIVERSAL提供了一些方法可供使用、继承和重写。
VERSION()方法
VERSION()方法会返回调用者(包或类)里的变量$VERSION的值。如果你提供了版本号作为参数且比调用者里$VERSION的值还要大,那就会产生一个异常。
假设模块HowlerMonkey的版本是1.23(也就是$VERSION的值是1.23),那么VERSION()的行为是这样的:
my $hm = HowlerMonkey->new;
say HowlerMonkey->VERSION; # prints 1.23
say $hm->VERSION; # prints 1.23
say $hm->VERSION( 0.0 ); # prints 1.23
say $hm->VERSION( 1.23 ); # prints 1.23
say $hm->VERSION( 2.0 ); # exception!
重写VERSION()方法要慎重,因为几乎不存在要重写的理由。
DOES()方法
DOES()方法是用来进行角色机制判断的,参数是调用者和角色名,如果调用者实现了该角色就返回真值(角色实现的机制可以有很多,如继承、委托、组成、角色应用或其他机制)。
默认DOES()的实现机制就是通过isa()方法,因为继承(isa()用于继承)就是这样一个机制:一个类实现了一个角色。假设给定一个类Cappuchin,那么它的DOES()行为可能是下面这样:
say Cappuchin->DOES( 'Monkey' ); # prints 1
say $cappy->DOES( 'Monkey' ); # prints 1
say Cappuchin->DOES( 'Invertebrate' ); # prints 0
某些情况下你可能会需要重写DOES()方法。
can()方法
can()方法用来判断调用者是否具有某方法。参数为方法名字符串,如果调用者具有该方法则返回该方法引用,否则就返回假值。你可以在类、对象、包上调用can()方法。
给定一个SpiderMonkey类,并且类中有screech方法:
if (my $meth = SpiderMonkey->can( 'screech' )) {...}
#将方法引用赋值给$meth
这样就能实现一种模式:调度方法前先检查该方法是否存在:
if (my $meth = $sm->can( 'screech' )
{
# method; not a function
$sm->$meth();
}
使用can()方法来测试一个包是否实现了一个指定的函数或方法:
use Class::Load;
die "Couldn't load $module!" unless load_class( $module );
if (my $register = $module->can( 'register' ))
{
# function; not a method
$register->();
}
Module::Pluggable
通过使用CPAN模块Class::Load能方便地进行模块加载。Module::Pluggable则能让你很容易地构建出插件管理系统。
isa()方法
isa()方法接受一个字符串,可以是类名或类型名(SCALAR, ARRAY, HASH, Regexp, IO,CODE)。可以把它当成类方法或实例方法来使用,如果调用者派生于或就是给定类(或调用者就是指定类型的bless过的引用)则返回真。
给定一个对象$pepper(一个哈希引用,类Monkey的神圣引用,继承自Mammal类),isa()方法的行为是这样的:
say $pepper->isa( 'Monkey' ); # prints 1
say $pepper->isa( 'Mammal' ); # prints 1
say $pepper->isa( 'HASH' ); # prints 1
say Monkey->isa( 'Mammal' ); # prints 1
say $pepper->isa( 'Dolphin' ); # prints 0
say $pepper->isa( 'ARRAY' ); # prints 0
say Monkey->isa( 'HASH' ); # prints 0
任何类都可以重写isa()方法。isa()方法有时候非常有用,比如如跟某些模块(Test::MockObject和Test::MockModule)协同工作时,或者代码没有使用角色时。要明白的是通常具有有充分的理由才会重写该方法。
类是否存在?
UNIVERSAL::isa()和UNIVERSAL::can()功能类似,你可以使用后者来安全地判断类是否存在,若UNIVERSAL::can($classname, 'can' )返回真,说明在某个地方定义了$classname类,但这个类存在却有可能不可用。
扩展UNIVERSAL
有个想法很有吸引力:将其他的方法也放到UNIVERSAL中,这样所有其他的类和对象中就都能使用这些方法了。但是,别这样做,特别是当代码不是你自己写的或者不是由你自己维护的时候,因为全局性的行为往往有着微妙的副作用。
偶尔借助UNIVERSAL来帮助调试修正代码还是可以的。例如Joshua benJore的UNIVERSAL::ref分发包就让几乎没用的ref()操作符变得有用。
UNIVERSAL::can和UNIVERSAL::isa分发包能帮助你调试多态相关的BUG。Perl::Critic也可以帮助你解决一些问题。
除非及其小心且有着实在而充分的理由,否则不应该将代码放到UNIVERSAL里面。