重载
Perl不是一切皆对象的语言。它的核心数据类型(标量、数字、哈希)都不是对象(当然也没有方法),但是你可以控制自己的类和对象在特定情形下(特定语境或特别的强制转换)行为,而这就是重载。
重载机制微妙而强大。一个有趣的例子就是重载后一个对象在布尔语境下的行为,通常在布尔语境中对象会评估为真值,但是你可以通过重载来改变这种行为。为什么想要改变这种行为呢?因为你可能期望达到这样一个效果:一个空的对象在布尔语境中被评估成假值。
你可以重载对象在这些情形(语境或操作符)下的行为:字符串化、数字化、布尔化、迭代、调用、数字访问、哈希访问、数字操作符、比较操作符、智能匹配、位操作符甚至是赋值。字符串化,数字化和布尔化是最重要、最常见的重载情形。(实际就是字符串语境、数字语境、布尔语境)
重载常见的操作符
编译指示overload用来连接要重载的符号和期望的行为。使用时参数时成对传递:要重载的符号为键,函数引用为值。一个名叫Null的类将重载在布尔语境下的行为,让它在布尔语境中总是返回假值:
package Null
{
use overload 'bool' => sub { 0 };
...
}
也很容易增加在字符串语境下的行为:
package Null
{
use overload
'bool' => sub { 0 },
'""' => sub { '(null)' };
}
重载数字化行为很复杂,因为算术操作符往往是2元的。若2个重载过的操作数相加,那么哪一个重载行为会先发生呢?这个答案必须要是一致且容易理解的,即使是没有读过源代码的人也要能理解。
在perldoc overload中使用2元操作符的调用约定和自动生成机制来解释。但是最简单的解决方案就是重载数字化行为('0+')并且提供一个回退方案:
package Null
{
use overload
'bool' => sub { 0 },
'""' => sub { '(null)' },
'0+' => sub { 0 },
fallback => 1;
}
设置fallback为真值就是让Perl可以使用任何其他定义过的重载行为来匹配请求的操作;如果仍然不行的话,Perl就当作没有重载过,这通常是你想要的行为。
如果没有fallback,Perl将只能使用你提供的重载行为。如果有人尝试执行额外的操作,Perl将抛出异常。
重载和继承
子类将会从父级(祖先)那继承重载行为。重写重载行为有2种方式。如果父类中的重载行为使用的是函数引用,那么子类也必须使用相同的形式来重写重载行为。
如果重载行为使用的是名字而不是函数引用,这样子类可以通过重写该方法来重写重载行为:
package Null {
use overload
'bool' => 'get_bool',
'""' => 'get_string',
'0+' => 'get_num',
fallback => 1;
sub get_bool { 0 }
}
这样任何子类都能通过重写get_bool()来改变布尔化情形下的行为:
package Null::ButTrue {
use parent 'Null';
sub get_bool { 1 }
}
使用重载
重载是一个用来产生新行为的捷径。CPAN上的IO::All分发包将这个想法发挥到了极限,实现了简单而优雅的API。很多的API都可以通过使用重载来变得焕然一新。
在矩阵类上重载加、乘、甚至连接操作后用起来就会更加顺手。当然这是因为本来矩阵里面就有这些操作符号,如果一个新的问题领域里并不存在这些符号,那么你就不得不自己想办法如何让Perl中的操作符来与之匹配。
《Perl最佳实践》对重载有着另外的建议:不要滥用。例如,对于不应该数字化表示的对象,将数字化重载为croak()函数,这样能帮助你发现和修复程序BUG。