程序员Perl小推车

第九章 管理真实的程序(六) -UNIVERSAL包

2016-03-30  本文已影响85人  可以没名字吗

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里面。

上一篇下一篇

猜你喜欢

热点阅读