本系列:
第3篇依赖于第2篇,第2篇依赖于1篇。
已有的代码结构
现在有父类Animal,子类Horse,它们的代码分别如下:
lib/Animal.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Animal;
sub speak {
my $class = shift;
print "a $class goes ",$class->sound(),"!\n";
}
sub sound { die 'You have to define sound() in a subclass'; }
1;
lib/Horse.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Horse;
use parent qw(Animal);
sub sound { "neigh" }
1;
一个perl程序speak.pl文件:
#!/usr/bin/env perl
use strict;
use warnings;
use lib "lib";
use Horse;
Horse->speak();
执行上面的speak.pl,将输出:
a Horse goes neigh
上面使用Horse->speak()
的方式调用speak()方法,它首先调用到父类Animal中的speak(因为Horse类中没有重写该方法),然后Animal中的speak又重新回调Horse类中的sound()。
这个speak是所有Horse都共享的,如果想要定义每个Horse对象都私有的数据呢?比如为每个Horse对象命名。这里Horse的名字就是Horse类的实例数据(在其它编程语言中常称之为成员变量),是每个对象独有的。
bless创建实例数据:对象
在Perl的面向对象编程中,一个对象表示的就是一个对内置类型的引用,比如标量引用、数组引用、hash引用。也就是说,所谓对象就是一个指向内置数据结构的引用,这个数据结构可以认为是每个对象私有的成员变量。
强烈建议使用hash引用的方式,不过此处先以标量引用的方式开始本文的介绍。
修改speak.pl文件:
#!/usr/bin/env perl
use strict;
use warnings;
use lib "lib";
use Horse;
my $name = "baima";
my $bm_horse = \$name;
bless $bm_horse,'Horse';
bless的语法为:
bless REFERENCE,CLASS;
它表示为CLASS类设置一个唯一标识符,并返回这个唯一标识符,这个唯一标识符是一个数据结构的引用,这个唯一标识符也被称为对象。也就是说,对象就是一个引用,所以我们常常会使用my $obj = bless REF,CLASS;
来返回一个对象。另一方面,bless表示将一个数据结构和类进行关联,表示这个数据结构(也许是空的,也许是经过一定初始化的)已经附加在类上,当创建这个类的实例(对象)时,它将返回一个引用,对这个数据结构的引用,换句话说,这个对象就已经拥有了这个数据结构。
如下图所示:
上面的Class是类,引用变量$obj_ref
是这个类的唯一标识符,它指向一个数据结构,这个数据结构是这个类的属性。使用Horse进行具体化,Horse是类,$bm_horse
是这个类的唯一标识符,这个引用指向值为"baima"的标量数据结构,所以$bm_horse
是一个对象,"baima"就是它独有的属性。
也就是说,bless $bm_horse,'Horse';
已经创建了一个名为$bm_horse
的对象,而"baimai"这个属性是只属于这一个对象的数据。
调用实例方法
bless生成一个引用后,这个引用是类的实例,可以通过这个引用变量去调用类的方法。
#!/usr/bin/env perl
use strict;
use warnings;
use lib "lib";
use Horse;
my $name = "baima";
my $bm_horse = \$name;
bless $bm_horse,'Horse';
print $bm_horse->sound(),"\n";
上面通过对象去调用类方法,它首先搜索出sound()在何处(即类中还是父类中),然后将参数传递给sound()。传递的参数列表中,第一个参数是实例的名称,也就是$bm_horse
,就像通过类名去调用类方法时,传递的第一个参数是类名一样。所以,下面两个是等价的:
$bm_horse->sound();
Horse::sound($bm_horse);
实际上,bless最初的目的就是通过一个引用来关联正确的类,以便perl能正确地找到所调用的方法,免去通过硬编码类名的麻烦。
再调用speak()试试:
$bm_horse->speak();
它将输出:
a Horse=SCALAR(0xc78610) goes neigh!
这是因为$bm_horse
是一个指向标量数据结构的引用,speak()方法中将其赋值给$class
,$class
也仍然是引用,而且speak()中并没有去解除这个引用,所以如此输出。至于解决方法,留待后文。
访问实例数据
因为实例的名称是每个对象的唯一标识符,而现在可以通过传递给方法的第一个参数获取实例的名称,借此名称,可以进一步地获取到该实例的其它数据。
现在,在lib/Horse.pm文件中添加一个name方法:
sub name {
my $self = shift;
$$self;
}
注意上面$$self
,因为$self
是对象名,而对象名总是一个引用变量,因此将其解除引用。
然后在speak.pl中调用这个方法:
#!/usr/bin/env perl
use strict;
use warnings;
use lib "lib";
use Horse;
my $name = "baima";
my $bm_horse = \$name;
bless $bm_horse,'Horse';
print $bm_horse->name()," says ",$bm_horse->sound(),"\n";
该print将输出:
baima says neigh
perl中几乎都使用$self
作为类名或对象名的代名词,就像java中的"this"一样。实际上,你可以使用任何变量名称,但约定俗成地,大家都喜欢用self。
通过构造器构造对象
前面构造Horse对象是在独立的speak.pl文件中实现的,这样生成Horse对象的方式是手动的,是完全私有的,构造对象时的实例数据(即name属性)也是完全暴露的。当在多个文件中都这样构造Horse对象,迟早会出错。
于是,在类文件lib/Horse.pm中定义一个构造方法new(),每次要构造Horse对象的时候只需调用这个方法即可。
#!/usr/bin/env perl
use strict;
use warnings;
package Horse;
use parent qw(Animal);
sub new {
my $class = shift;
my $name = shift;
belss \$name,$class;
}
sub name {
my $self = shift;
$$self;
}
sub sound { "neigh" }
1;
上面在Horse.pm中定义了一个new()方法,该方法里面包含了bless语句,且作为new()方法的最后一个语句,表示构造一个对象并返回这个对象的唯一标识符:引用变量表示的对象名。因此,这个new()方法被称之为构造方法:用于构造该类的实例。
方法名new()可以随意,例如hire(),named()等都可以,但面向对象编程语言中,基本上都使用new这个词语来表示创建新对象,所以,也建议采用约定俗成的new(),如果使用其它方法名作为构造方法,请做好注释。
现在,只要调用Horse中的这个new()方法,就表示在当前包中构建一个Horse的实例(bless的返回值):
my $bm_horse = Horse->new("baima");
注意,bless返回的是对象引用,所以赋值给变量$bm_horse
,这时$bm_horse
将代表这个对象,是这个对象的唯一标识符。
上面调用new()的过程中,首先找到类方法new(),然后传递参数列表('Horse',"baima")
,new()方法中,bless将baima这个数据结构附加到Horse类中,并返回指向该数据结构的引用。以后,通过$bm_horse
就能找到这个数据结构,因为这个数据结构是对象$bm_horse
的实例数据。
继承构造方法
在上面lib/Horse.pm中的构造方法new()中是否有Horse所特有的个性内容?完全没有。无论是Horse、Cow还是Sheep的构造方法都是通用的,所以将共性的代码抽取到父类Animal中。
lib/Animal.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Animal;
sub new {
my $class = shift;
my $name = shift;
bless \$name,$class;
}
sub name {
my $self = shift;
$$self;
}
sub speak {
my $class = shift;
print "a $class goes ",$class->sound(),"!\n";
}
sub sound { die 'You have to define sound() in a subclass'; }
1;
lib/Horse.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Horse;
use parent qw(Animal);
sub sound { "neigh" }
1;
如此一来,无论是Horse、Cow还是Sheep都继承父类Animal中的构造方法new()以及name()。注意,上面name()方法也抽取到了Animal类中,因为它也是共性的,不过本小节暂时用不到该方法,下一小节会修改该方法。
现在,在speak.pl文件中构建一个Horse对象:
my $bm_horse = Horse->new("baima");
然后通过这个对象调用speak方法:
$bm_horse->speak();
它将输出:
a Horse=SCALAR(0xc78610) goes neigh!
这个实验前文已经验证过了。之所以会如此,是因为传递给speak()的第一个参数是对象的引用变量,而speak()中并没有去解除这个引用。再次看看speak()的代码:
sub speak {
my $class = shift;
print "a $class goes ",$class->sound(),"!\n";
}
speak()中的$class期待的其实是一个类名,而不是对象名,因为类名是具体的字符串,而非引用变量。例如,使用Horse->speak()
就不会出现上面的问题。
如何解决父类中的这种问题,使其能同时处理类名和对象名?
让方法能同时处理类和对象
为了让父类中的方法能同时处理类名和对象名,可以加入一个额外的方法对类名和对象名进行判断。如何判断是类名还是对象名?只需使用ref即可,如果ref能返回一个值,表示这是一个引用,说明这是对象,ref返回false,则说明这不是引用,也就是类名。
之前因为name()方法因为共性的原因被抽取到Animal.pm后并没有使用过,这里派上用场了。
lib/Animal.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Animal;
sub new {
my $class = shift;
my $name = shift;
bless \$name,$class;
}
sub name {
my $self = shift;
ref $self ? $$self : "an unamed Class $self"; # 修改此行
}
sub speak {
my $class = shift;
print $class->name()," goes ",$class->sound(),"!\n"; # 调用name()方法
}
sub sound { die 'You have to define sound() in a subclass'; }
1;
这样speak()就变得共性化,既能处理类名,也能处理对象名。
my $bm_horse = Horse->new("baima");
$bm_horse->speak(); # 传递对象名
Horse->speak(); # 传递类名
将输出如下结果:
baima goes neigh!
an unamed Class Horse goes neigh!
之所以加入新的方法,是因为在speak()中类名和对象名是相互独立的,也就是无法共性的,要么是类名,要么是对象名。为了让一段代码共性化,解决方法就是添加额外的代码将非共性内容化解掉,这些额外的代码可以直接加在speak()内部,也可以放进一个新定义的方法中,然后在speak()中调用这个方法。这是一种编程思想。
使用hash数据结构:添加额外的成员变量
经常地,perl使用hash作为对象的数据结果,这个数据结构中可以存储不同的数据、引用,甚至是对象,其中hash的key常作为实例数据(成员变量)。
再次说明,perl面向对象时最常用的对象数据结构是hash,但标量、数组也一样可以,至少很少用。
想要使用hash数据结构,只需将一个hash结构bless到类上即可。它表示这个hash数据结构附加在类上,bless返回一个引用,这个引用就是对象,所以这个对象指向这个数据结构,从而对象拥有这个数据结构。
例如,绑定一个空的hash结构:
bless {},$class;
上面的bless将一个匿名hash附加到类中。
对于父类Animal来说,由于已经有了name的属性,现在如果想要加上一个color属性,就可以将这两个成员属性放进一个hash结构中:
lib/Animal.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Animal;
sub new {
my $class = shift;
my $name = shift;
my $self = {
Name => $name,
Color => $class->default_color(),
};
bless $self,$class;
}
sub name {
my $self = shift;
ref $self
? $self->{Name} # 此处需要修改,因为$self不再是标量引用变量,而是hash引用变量
: "an unamed Class $self";
}
sub default_color {
die "You have to override default_color method in subclasses";
}
sub speak {
my $class = shift;
print $class->name()," goes ",$class->sound(),"!\n";
}
sub sound { die 'You have to define sound() in a subclass'; }
1;
上面将一个包含key:Name和Color的hash数据结构bless到类上,其中Name成员变量通过构造对象时传递参数赋值,Color则调用各类自己的默认颜色方法default_color(),各个子类必须重写该方法。这是显然的,我们可以为某一子类动物设置默认毛色,但不能为所有动物设置同一种默认毛色。
然后修改lib/Horse.pm和lib/Sheep.pm,重写default_color():
lib/Horse.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Horse;
use parent qw(Animal);
sub sound { 'neigh' }
sub default_color {
'black'
}
1;
lib/Sheep.pm中:
#!/usr/bin/env perl
use strict;
use warnings;
package Sheep;
use parent qw(Animal);
sub sound { 'baaaah' }
sub default_color {
'white'
}
1;
然后,speak.pl中构造Horse对象和Sheep对象,并访问自己的成员属性:
my $bm_horse = Horse->new("baima");
my $by_sheep = Sheep->new("xiaoyang");
print $bm_horse->{Name},"\n";
print $bm_horse->{Color},"\n";
print $by_sheep->{Name},"\n";
print $by_sheep->{Color},"\n";
结果:
baima
black
xiaoyang
white
子类重写构造方法
从父类中继承构造方法时,创建的对象的数据结构是完全一致的。如果某个子类想要多添加一些固定的数据元素,可以让子类重写父类的构造方法。
但需要注意的是,重写方法时,一般都强烈建议只对父类方法进行扩展,而不应该否定父类方法,完全修改父类方法(抽象方法除外)。
例如,现在父类Animal中的构造方法如下:
sub new {
my $class = shift;
my $name = shift;
my $self = {
Name => $name,
Color => $class->default_color(),
};
bless $self,$class;
}
想要为子类Horse添加一种固定的属性,马的类型是战马、比赛用的马还是普通的马。于是,在Horse类中:
package Horse;
use parent qw(Animal);
sub new {
my $self = shift->SUPER::new(@_);
$self->{Type} = "Racehorse";
$self;
}
注意,上面Horse中的构造方法new()中并没有给bless语句。当调用Horse->new()
构建对象的时候,首先调用父类的new(),父类的new会关联一个hash结构并返回这个hash结构,这个hash结构又赋值给$self
,为此hash结构添加一种元素后,子类的new()返回$self
,使得这个hash结构成为子类对象的数据结构。
为了后面的实验,本节所修改的Horse内容请删除。
设置和获取实例数据:setter & getter
上面设置Color的时候只能通过方法default_color()设置默认的毛色,但马有黑马、棕色马、条纹马等等,所以需要能手动设置各种颜色。此外,还要更及时获取到当前最新的成员变量值,比如获取某Horse对象的名称和颜色。这就是俗称的setter和getter方法的作用。
在此示例中,Name属性是直接通过构造方法传值设置的,在逻辑上它唯一标识这个对象(对我们而言,对perl而言是通过对象引用来唯一识别的),所以Name属性不应该允许重新设置。再者,因为设置和获取各对象的属性的代码是共性的,所以直接将这两类方法写到父类Animal中。
lib/Animal.pm中新加的代码片段:
sub set_color {
my $self = shift;
$self->{Color} = shift;
}
sub get_color {
my $self = shift;
$self->{Color};
}
sub get_name {
shift->{Name};
}
现在可以为每个Horse或Sheep对象都设置对象自己的颜色,并且能获取颜色和名称:
my $bm_horse = Horse->new("baima");
my $by_sheep = Sheep->new("xiaoyang");
$bm_horse->set_color("white-and-black");
print $bm_horse->get_color(),"\n";
print $by_sheep->get_name(),"\n";
结果如下:
white-and-black
xiaoyang
注意上面get_name()中的一种简写方式:shift->{NAME}
,shift没有给参数,所以它的操作对象是@_
,它等价于(shift @_)->{Name}
,也等价于:
my $self = shift;
$self->{Name};
关于setter返回值的问题
在为setter方法进行编码的时候,需要考虑它的返回值,一般来说有以下4种返回值类型:
- (1).set成功后的值
- (2).set之前的值
- (3).返回对象自身
- (4).返回成功/失败布尔值
这4种返回值各有优缺点,但无论如何都请注释好返回值的类型,并且设计好之后就别再修改。
第(1)种是最通用、最常见也最简单的行为,传递什么参数给setter,就返回什么参数值,正如set_color()一样:
sub set_color {
my $self = shift;
$self->{Color} = shift;
}
一般来说,这种setter方法是放在空上下文(void context)中执行的,但在perl中也可以直接输出它:print set_color("COLOR")
。
第(2)种要返回设置之前的值,也很简单,只需使用一个临时变量存储一下原始值并返回该变量即可:
sub set_color {
my $self = shift;
my $temp = $self->{Color};
$self->{Color} = shift;
$temp;
}
这里有一点点小优化。因为是set,所以它可能是在空上下文中执行的,也就是说这时返回之前的值是多余的。可以通过wantarray
来判断一下,wantarray函数用于检查执行上下文,如果在列表上下文中则返回true,标量上下文中则返回false,空上下文中则返回undef。
sub set_color {
my $self = shift;
if(defined wantarray){
# 非空上下文,返回值有用
my $temp = $self->{Color};
$self->{Color} = shift;
$temp;
} else {
# 空上下文,无需返回值
$self->{Color} = shift;
}
}
第(3)种返回对象自身:
sub set_color {
my $self = shift;
$self->{Color} = shift;
$self;
}
一般来说不会用到这种情况。但有时候有奇效,例如可以形成对象链。例如,Person有4个成员变量:Name,Age,Height,Weight,它们的setter方法都返回对象自身,那么可以:
my $people = Person->set_name("abc")->set_age(23)->set_height(168)->set_weigth(60);
# 格式化一下:
my $people =
Person->set_name("abc")
->set_age(23)
->set_height(168)
->set_weigth(60);
第(4)种返回布尔值有时候非常有效,特别是对于经常更新出错的情况。如果是前3种返回值方式,会抛出异常,需要判断并使用die进行终止。
别暴露实例数据
在面向对象编程中,常使用一个术语don't look inside box
来表示不要暴露对象的成员数据。
通过$obj_ref->{KEY}
的方式可以在类的外部访问或设置类的数据结构(成员变量),这是违反对象封装原则的,它将每个对象的内部属性都暴露出来了。对象就像是一个黑盒子,$obj_ref->{KEY}
就像是将锁链撬开一样。
面向对象的目的之一是让Animal或Horse的维护者可以对它们的方法能独立地做出合理的修改,并且修改后那些已经导出的接口仍然能够正常工作。为什么直接访问hash结构违反了这个原则?当Animal的Color属性不再使用颜色的名称作为它的值时,而是使用RGB三原色的方式来存储颜色呢?
在此示例中,以一个虚构的模块Color::Conversions
来修改颜色数据的格式,该模块有两个函数rgb_to_name()和name_to_rgb(),用于转换RGB和颜色的字符串名称,其中name_to_rgb()返回的是一个包含RGB三原色的数组引用。
可以修改set_color()和get_color()方法:
use Color::Conversions qw(rgb_to_name name_to_rgb);
sub set_color {
my $self = shift;
my $color_name = shift;
$self->{Color} = name_to_rgb($color_name);
}
sub get_color {
my $self = shift;
rgb_to_name($self->{Color});
}
现在我们可以照旧使用setter和getter,但内部其实已经改变了,这些改变对使用者来说是透明的。此外,我们还可以添加额外的接口,使得我们可以直接设置RGB格式的颜色:
sub set_color_rgb {
my $self = shift;
$self->{Color} = [@_];
}
sub get_color_rgb {
my $self = shift;
@{ $self->{Color} };
}
如果我们在类的外面直接使用$bm_horse->{Color}
,将无法直接查看,因为它是一个RGB三原色元素列表的引用,而非直接显示出来的RGB元素值或颜色名称。
这正是面向对象编程所鼓励的行为,对于perl而言,只需将成员变量对应的值设置为一个引用即可。以关联hash数据结构的Animal类为例:
Animal
|--------------------------|
|-> KEY1 => $ref_value1 |
|-> KEY2 => $ref_value2 |
|-> KEY2 => $ref_value2 |
|--------------------------|
为了让数据通过引用的方式隐藏起来,且能通过getter方法查找出来,需要合理设计setter和getter方法。例如,让setter以普通的字符串为参数,但却将其存储到一个引用中,让getter以引用为参数,但却返回人眼可识别的内容。
简化setter和getter的书写
对于面向对象来说,这两个方法写的实在太频繁了。perl一切从简的原则,自然也要将其简化书写:
sub get_color { $_[0]->{Color} }
sub set_color { $_[0]->{Color} = $_[1] }
或者:
sub get_color { shift->{Color} }
sub set_color { pop->{Color} = pop }
合并getter和setter
如果不考虑默认传递的类名或对象名参数,getter方法通常是不含参数的,setter方法通常是包含参数的。通过这个特性,可以将getter和setter合并起来:
sub get_set_color {
my $self = shift;
if(@_) {
# 有参数,说明是setter
$self->{Color} = shift;
} else {
# 没有参数,说明是getter
$self->{Color};
}
}
或者简写的:
sub get_set_color { @_ > 1 ? pop->{Color} = pop : shift->{Color} }
使用时:
# 设置颜色
$bm_horse->get_set_color("blue");
# 获取颜色
print $bm_hore->get_set_color(),"\n";
限制方法类别:类方法和实例方法
在perl中所有的方法都是子程序,没有额外的功能来区分一个方法是类方法还是实例方法。对我们来说,如果传递的第一个参数为类名的是类方法,如果传递的第一个参数为对象引用名的方法是实例方法。
好在perl提供了ref函数,可以通过检查引用的方式来检查这是类方法还是实例方法。
use Carp qw(croak);
sub instance_method {
ref(my $self = shift) or croak "this is a instance method";
...CODE...
}
sub class_method {
ref(my $class = shift) and croak "this is a Class method";
...CODE...
}
这里使用croak替换了die,这样报错的时候可以直接告诉错误所在的行数。
私有方法(private method)
私有方法是指类中不应该被外界访问的方法,它可以在类自身其它地方调用,但不应该被对象或其它外界访问以免破坏数据。
例如,类Class1中的方法get_total()
内部调用一个私有方法_get_nums()
,通过该私有方法返回的数组来获取一个包含数值的数组。如果有一个子类Class2继承了Class1,且重写了_get_nums()
使其返回一个数组引用而非数组,这时对象要调用的get_total()
整段代码就废了。
package Class1;
sub new {
my ($class,$args) = @_;
return bless $args,$class;
}
sub get_total {
my $self = shift;
my @nums = $self->_pri_sub; # 期待该私有方法返回一个列表
my $total = 0;
foreach (@nums) {
$total += $_;
}
return $total;
}
sub _pri_sub {
my $self = shift;
...some codes...
return @nums; # 返回一个数组
}
1;
一般来说,这样的问题并不常发生,因为程序毕竟是程序员写的,遵循规范的情况下,大家都知道这是什么意思。但如果程序比较庞大,也许无意中就重写了一个私有方法。面向对象,一个最基本的规则就是保护数据不被泄漏、不被破坏。
但perl中所有的方法都是public(公共的),谁都能访问,并没有提供让方法私有化的功能。只是以一种呼吁式的规范,让大家约定俗成地使用下划线"_"作为方法名的前缀来表示这是一个私有方法(例如sub _name {}
)。但这只是一种无声的声明"这是私有方法,外界请别访问",perl并不限制我们从外界去访问下划线开头的方法。
要实现方法的真正私有化,可以将匿名子程序赋值给一个变量来实现,或者通过闭包的方式实现。
sub get_total {
my $self = shift;
my @nums = $self->$_pri_sub(ARGS);
my $total = 0;
foreach (@nums) {
$total += $_;
}
return $total;
}
my $_pri_sub = sub {
my $self = shift;
...some codes...
return @nums;
}
需要注意的是,上面$self->$_pri_sub()
中箭头的右边是一个变量,在其它面向对象语言中是无法将变量作为方法名的,但perl支持。
因为$_pri_sub
是词法变量,构造的对象无法取得这样的数据。但通过一些高级技术,对象还是能够取得这个方法,要想完全私有化,通过闭包实现:
sub get_total {
my $self = shift;
my $_pri_sub = sub {
my $self = shift;
...some codes...
return @nums;
}
my @nums = $self->$_pri_sub(ARGS);
my $total = 0;
foreach (@nums) {
$total += $_;
}
return $total;
}
祖先类:UNIVERSAL
UNIVERSAL是一切类的祖先,所有的类都继承于它。它提供了3个方法:isa()、cat()和VERSION(),在v5.10.1和之后,还提供了另一个方法DOES()。
1.isa()用于判断某个给定的对象(或类)与某个类是否满足is a
的关系,也就是"对象是否是某个类的实例,类1是否是类2的子类"。
$object_or_class->isa(CLASS);
2.can()用于判断某个给定的对象(或类)是否能够使用某方法。
$object_or_class->can($method_name);
需注意,can()方法检测结果为真的时候,其返回值是该方法的引用。也就是说,可以直接将can()赋值给一个方法的引用变量,避免多次书写。以下是等价的写法:
if(my $method = $obj->can($method_name)){
$obj->$method;
}
if($obj->can($method_name)){
$obj->$methdo_name;
}
3.VERSION()用于返回对象(或类)的版本号。
设置了our $VERSION=...;
之后,既可以通过继承自UNIVERSAL的VERSION()方法获取版本号,也可以通过对象获取VERSION变量值:
$obj->VERSION();
$obj->VERSION;
多重继承
Perl支持多重继承。假设Class3多重继承Class1和Class2:
package Class3;
# 1.
use base qw(Class1 Class2);
# 2.
use parent qw(Class1 Class2);
# 3.
use Class1;
use Class2;
our @ISA = qw(Class1 Class2);
但无论是哪种语言,都强烈建议不要使用多重继承。
假设Class1和Class2都直接继承自UNIVERSAL类,现在Class3多重继承Class1和Class2。
UNIVERSAL
| |
Class1 Class2
| |
Class3
假设Class2有方法eat(),Class1没有,且Class3没有写eat(),那么Class3的实例调用eat()方法的时候,会调用到Class2的eat()吗?
默认情况下,Perl搜索方法的规则是从左搜索,深度优先。意味着use base qw(Class1 Class2)
时,当Class3自身找不到eat()时,将先搜索左边的Class1,搜索完没发现eat(),将搜索Class1的父类UNIVERSAL。也就是说永远也不会去搜索Class2。
实际上,搜索父类时是搜索@ISA
中的元素,所以是从左开始搜索。
但是可以使用CPAN上的C3或者mro模块,它们实现从左搜索,广度优先的搜索规则。也就是说,对于use base qw(Class1 Class2)
,当Class3自身找不到eat()时,将先找左边的Class1的eat(),找不到再找右边的Class2的eat(),还找不到的话最后找父类的eat()。