【转】【Perl 文档中文化计划】《Perl 对象》(中级教程)翻译完成。
http://www.chinaunix.net 作者:flw 发表于:2009-06-17 10:10:57
最新版本可以从这里获取(POD 格式):
http://svn.perlchina.org/trunk/POD2-CN/lib/POD2/CN/perlobj.pod
翻译过程中,有两位不愿意透露 ID 的本坛斑竹向我提供了建设性的意见,
在此向他们表示感谢。
NAME
perlobj - Perl 对象
说明
首先你必须懂得在 Perl 中,什么叫做“引用”,如果你还不懂,那么请参考
perlref。 其次,如果你仍然觉得下文出现的引用过于复杂的话,那么请先阅读
perltoot 和 perltooc 这两个 Perl 的面向对象编程初级教程。
首先让我们来看看有关 Perl 面向对象编程的三个基本定义:
1. 一个“对象”是指一个“有办法知道它是属于哪个类”的简单引用。
2. 一个“类”是指一个“有办法给属于它的对象提供一些方法”的简单的包。
3. 一个“方法”是指一个“接受一个对象或者类名称作为第一个参数”的简单的
子程序。
我们暂时不考虑从更深一层的角度来讲,以上说法是否正确。
对象仅仅只是引用
和 C++ 不同,Perl 没有为“构造函数”提供任何特殊的语法(译者注:在 C++
中,和类名称相同的类方法被称为“构造函数”,创建对象时被自动调用)。Perl
中,构造器(译者注:因为 Perl 不强调“函数”这个概念,因此从下文中一律译
为“构造器”)只是一个会返回一个“经过 bless 处理”的引用的子程序,这个
经过 bless 处理的引用就是人们所说的“对象”,而 bless 的作用就是用来说明
这个对象是隶属于哪个“类”。
下面就是一个典型的构造器的例子:
package Critter;
sub new { bless {} }
new 这个词并没有任何特殊的含义,如果你喜欢,你也可以写成这样:
package Critter;
sub spawn { bless {} }
这样做不会使 C++ 程序员误以为 "new " 有什么特殊的含义,因此或许更加合理一
些。我们建议你给你的构造器起名时尽量选择能够准确反应它在你的解决方案中的
意义的名字。而不要拘泥于 new 或者其它那些千篇一律的名字。例如在 Perl/Tk
中,组件的构造器就叫做“create”。
和 C++ 相比,Perl 的构造器有一点不同,那就是它不会自动调用基类的构造器。
因为 hash 可以轻易地表示“名字=> 值”这样的属性对,因此通常我们用一个匿名
hash 引用来储存对象的各个属性。在上例中,用一对大括号 "{} " 可以生成一个
空的匿名 hash 引用,然后 bless() 函数给它打上一个印记,让它变成一个
Critter 类的对象,最后返回这个对象。这么做只是为了图个方便,因为对象自身
知道它是被 bless 过的,并且 bless {} 正好是 sub new 的最后一个语句(也是
唯一的语句),所以可以直接做为返回值,不需要显式地 return。
实际上,sub new { bless {} } 写全了相当于下面的代码段:
sub new {
my $self = {};
bless $self;
return $self;
}
有时候你经常会见到更复杂一些的构造器,比如它可能会调用另外一个方法去做一
些构造工作:
sub new {
my $self = {};
bless $self;
$self-> initialize(); # 注意这里
return $self;
}
如果你小心地处理继承的话(这种情况经常碰到,参见
" "模块的创建、使用和重用 " in perlmodlib),那么你可以用两个参数来调用
bless,因此你的构造器就可以实现继承:
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
$self-> initialize();
return $self;
}
如果你希望用户不仅能够用 "CLASS-> new() " 这种形式来调用你的构造函
数,还能够以 "$obj-> new() " 这样的形式来调用的话,那么就这么做:
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
bless $self, $class;
$self-> initialize();
return $self;
}
需要注意的是,这样作并不会发生任何拷贝动作。如果你希望拷贝一个对象,那
么你需要自己写代码处理。接下来 bless 的第二个参数 $class 所属的
initialize() 方法将被调用。
在类的内部,所有的方法都把对象当作一个普通的引用来使用。而在类的外部,用
户只能看到一个经过封装的对象,所有的值都是不透明的,只能通过类的方法来访
问。
虽然理论上我们可以在构造器中重新 bless 一个对象到别的类。对一个对象再次
进行 bless,将导致这个对象术语新类,而忘记原先的老类。我们应该保持一个对
象始终只属于一个,所以我们不建议这么做。但是如果有谁真的这么做,那纯粹是
自找麻烦。
澄清一下:对象是经过 bless 的,引用变量则没有。对象知道它被 bless 到了哪
个类,而引用变量不知道。bless 处理的实际上是引用指向的对象,而不是引用变
量自身。考虑下面的例子:
$a = {};
$b = $a;
bless $a, BLAH;
print "\$b is a ", ref($b), "\n ";
结果显示 $b 也被 bless 到 BLAH 类了,由此可见,bless() 操作的是对象而不
是引用变量。
一个类只是一个简单的包
和 C++ 不同,Perl 并不为类定义提供任何特殊语法。实际上类只是一个包而已。
你可以把一个包当作一个类用,并且把包里的函数当作类的方法来用。
不过,有一个特殊的数组,叫做 @ISA,它说明了“当 Perl 在当前包中找不到想
要的方法时,应当继续从哪儿去找”。这就是 Perl 实现“继承”的关键。@ISA
中的每个元素都是一个别的包的名字。当类找不到方法时,它会从 @ISA 数组中
依次寻找(深度优先)。类通过访问 @ISA 来知道哪些类是它的基类。
所有的类都有一个隐含的基类(祖先类): "UNIVERSAL "。 "UNIVERSAL " 类为它
的子类提供几个通用的类方法。参见 "默认的 UNIVERSAL 方法 " 得到更多说明。
如果在基类中找到了缺失的方法,那么为了提高效率,它会被缓存到当前类。每
当修改了 @ISA 或者定义了新的子程序时,缓存会失效,这将导致 Perl 重新做
一次查找。
如果在当前类、当前类所有的基类、还有 UNIVERSAL 类中都找不到请求的方法,
这时会再次查找名为 AUTOLOAD() 的一个方法。如果找到了 AUTOLOAD,那么就会
调用,同时设定全局变量 $AUTOLOAD 的值为缺失的方法的全限定名称。
如果还不行,那么 Perl 就宣告失败并出错。
如果你不想继承基类的 AUTOLOAD,很简单,只需要一句
sub AUTOLOAD;
就行了。然后调用 AUTOLOAD 时就会失败。
Perl 类只有方法继承。数据继承由程序员自己实现。基本上,对 Perl 来讲这不
是一个什么大问题:因为我们大多数时候都用匿名 hash 来储存对象数据,而每
一层的基类都可以往 hash 表中加入自己的属性,因此子类自然就可以继承基类
的属性。唯一的问题发生在基类和子类使用了同一个名字作为 hash 键值时。不
防假设基类已经使用了 'city ' 这个键名,这时子类中也想用 'city ' 这个键,
那么很明显将会覆盖,由于子类在设计时无法知道父类中是否已经使用了 'city '
所以似乎这的确是一个问题。有一个变通方法就是,每一层类都优先考虑使用自
己的包名称作为 hash 键的前缀:
sub bump {
my $self = shift;
$self-> { __PACKAGE__ . ".count "}++;
}
这样你就可以在父类和子类中访问同一个属性的不同版本。
一个方法就是一个简单的子程序
和 C++ 不同,Perl 不提供任何特殊的语法来定义方法。(不过 Perl 提供了一
个特殊的语法用来调用方法,稍后再讲)。方法把它被调用时的对象或者类名称
当作它的第一个参数。有两种不同的调用方法的途径,分别成为“调用类方法”
和“调用实例方法”。
类方法把类名当作第一个参数。它提供针对类的功能,而不是针对某个具体的对
象的功能。构造器通常是一个类方法,参见 perltoot 或者 perltooc。
大多数类方法简单地忽略第一个参数,因为方法知道自己处在什么类里面,也不
关心它是通过什么类来调用的。(调用类和所处类不一定相同,例如基类的方法
被子类调用时,方法的所处类是基类,而调用类是子类,类方法和对象方法都是
如此。) 举个常见的例子,下面的类方法可以通过名字来查询对象:
sub find {
my ($class, $name) = @_;
$objtable{$name};
}
实例方法把对象作为它的第一个参数。因此典型的做法是把第一个参数 shift
到一个名为“self”或者“this”的变量中。然后再把它当作一个引用来用:
sub display {
my $self = shift;
my @keys = @_ ? @_ : sort keys %$self;
foreach $key (@keys) {
print "\t$key => $self-> {$key}\n ";
}
}
调用方法
出于历史遗留的原因,Perl 提供了两种不同的形式去调用一个方法。最简单的
形式是采用箭头符号:
my $fred = Critter-> find( "Fred ");
$fred-> display( "Height ", "Weight ");
你可以早就熟悉了引用的 "-> " 操作符。事实上,因为上面的 $fred
是一个指向了对象的引用,因此你也可以把箭头操作符理解为另外一种形式的
解引用。
出现在箭头左边的引用或者类名,将作为第一个参数传递给箭头右边的方法。
所以上面的代码就分别相当于这样:
my $fred = Critter::find( "Critter ", "Fred ");
Critter::display($fred, "Height ", "Weight ");
Perl 怎么知道箭头右边的子程序是哪个包里的呢?答案是通过查看箭头左边的
内容。箭头左边必须是一个对象,或者是一个标识类名的字符串。这两种情况
都行。如果类里没有这个方法,那么 Perl 就从基类中进行检索。
如果必要,你还*可以*强制 Perl 检索其它类:
my $barney = MyCritter-> Critter::find( "Barney ");
$barney-> Critter::display( "Height ", "Weight ");
这个例子中, "MyCritter " 类是 "Critter " 类的子类,并且定义了自己的 find()
和 display() 方法。通过加前缀 "Critter:: " 可以强制 Perl 执行 "Critter "
的方法而不是 "MyCritter " 自己的方法。
上面的例子还有一种特殊情形,那就是你可以用 "SUPER " 伪类来告诉 Perl
通过当前包的 @ISA 数组来检索究竟应该使用哪个类。
package MyCritter;
use base 'Critter '; # sets @MyCritter::ISA = ( 'Critter ');
sub display {
my ($self, @args) = @_;
$self-> SUPER::display( "Name ", @args);
}
注意: "SUPER " 表示 *当前包* 的 超类 而不是 *对象* 的 超类。
而且, "SUPER " 符号仅仅只是一个方法名称的 修饰符,因此不能把它当作
类名称使用在其它地方。记住: "SUPER " 不是类名称,只是修饰符。例如:
something-> SUPER::method(...); # OK
SUPER::method(...); # WRONG
SUPER-> method(...); # WRONG
最后一点,箭头左边的类名或者对象,也可以用返回类名或者对象的表达式来代
替。所以下面这句是合法的:
Critter-> find( "Fred ")-> display( "Height ", "Weight ");
这句也是合法的:
my $fred = (reverse "rettirC ")-> find( "Fred ");
[解决办法]
lu guo....
[解决办法]
不错,虽然没怎么看
[解决办法]
哦原来是这个样子啊
[解决办法]
学习了 谢谢哈
[解决办法]
谢谢分享, 学习学习。。。
[解决办法]
谢谢分享, 学习学习。。。
[解决办法]
纯学习储备。。。。
------解决方案--------------------
顶了先
[解决办法]
学习学习
[解决办法]
DING
[解决办法]
xuexile
[解决办法]
终于看懂了。
[解决办法]
good
[解决办法]
flw好熟的字眼啊,perl 在cu的斑竹嘛
[解决办法]
不错的啊 好东西 看看
[解决办法]
UP
[解决办法]
学习一下
[解决办法]
牛人向你学习没道理
[解决办法]
perl还没有开始学习,要好好看一下
[解决办法]
呵呵,进来接分来着
[解决办法]
学习,学习
[解决办法]
看来值得学习一下呀。