#!/usr/bin/perl
=pod
[1]对象就是一个引用
[2]类就是包
[3]方法就是子例程
=cut
#方法调用
=pod
对于类方法而言,调用者是包的名字
对于实例方法而言,调用者是指定对象的引用
=cut
#名词解释 INVOCANT 方法调用者
#[1] 使用箭头操作符的方法调用
=pod
INVOCANT->METHOD(LIST)
INVOCANT->METHOD
调用者->方法
如果INVOCANT是一个用,我们就称这个方法是以实例方法调用的
如果INVOCANT是一个包的名字,我们把这个被调用的METHOD看作类方法
=cut
use DBI;
$dbh = DBI->connect($data_source, $username, $auth, \%attr); #类方法
$dbh->do($statement); #实例方法
#learning
$image = Wizard->summod("Gandalf"); #类方法
$image->speak("friend"); #实例方法
#summod and speak 都是由Wizard类定义的
#合并
$image->summod("Gandalf")->speak->("friend");
#[2] 使用间接对象方法的方法调用
=pod
METHOD INVOCANT(LIST)
METHOD INVOCANT LIST
METHOD INVOCANT
=cut
$image = summod Wizard "Gandalf";
$nameis = summod Balrog home => "Moria", weapon => "whip";
move $nameis "bridge";
break $staff;
#[3] 引用包的类
=pod
使用间接调用容易混淆
有一种方法可以解决这样的混淆,而同时仍然保持间接对象的语法:通过在包后面附加两个冒号引用类名
$obj = method CLASS:: #强制为"CLASS->method"
#下面的方法我们经常看到:
$obj = new CLASS #不会解析为方法
=cut
$obj = new ElvenRing; #可以是new("ElvenRing")
#也可以是new(ElveRing())
$obj = ElverRing->new; #可以是ElvenRing()->new
$obj = new ElvenRing::; #总是"ElvenRing"->new()
$obj = ElvenRing::->new; #总是"ElvenRing"->new()
#[4]构造对象
=pod
所有的对象都是引用,但不是所有的引用都是对象.
把引用和包名字标记起来(因此也和包中的类标记起来了,因为一类就是一个包)的动作
被称作bless,你可以把bless看作是把引用转换成对象,尽管更准确地说是把该引用转换
成一个对象引用。
bless函数接受一个或者两个参数.第一个是参数是引用,而第二个是把要引用bless成的包。
如果忽略第二个参数,则使用当前包.
=cut
$obj = {}; #指把引用放到一个匿名散列
bless($obj); #bless散列到当前包
bless($obj,"Critter"); #bless 散列到类Critter
#一个典型的构造器:
package Critter;
sub spawn {bless{};}
#或者略明确的拼写
package Critter;
sub spwan
{
my $ref = {}; #指向一个空的匿名散列
bless $ref,"Cirtter"; #把那个散列制作一个Cirtter对象
return $ref; # 返回新生成的Cirtter
}
$pet = Cirtter-spwan;
#[5] 可继承构造器
=pod
和所有方法一样,构造器只是一个子例程,但我们不把它看作子例程,在这个例子中,我们总是把它当作方法
来调用----一个类方法,因为调用者是一个包名字。
=cut
=pod
方法调用 | 结果子例程调用
---------------------------------------------------
Cirtter->spwan() | Cirtter::spwan("Cirtter")
Spider->spwan() | Cirtter::spwan("Spider")
---------------------------------------------------
两种情况调用的子例程都是一样的,但是参数不一样。请注意我们上面
的spwan构造器完全忽略了它的参数,这就意味着我们的Spider对象被错
误地bless成了Cirtter类。一个更好的更好的构造器将提供包名字给bless:
=cut
sub spwan
{
my $class = shift; #存储包的名字
my $ref = {};
bless($ref,$class); #bless该引用到包中
return $ref;
}
#现在你可以为两种情况使用同一个子例程
$vermin = Cirtter->spwan;
$shelob = Spider->spwan;
#对象或者类名一起工作
sub spwan
{
my $invocant = shift;
my $class = ref($invocant) ||$invocant;
my $self = {};
bless ($self,$class);
return $self;
}
#[6]初启程序
=pod
大多数对象维护的信息是由对象的方法间接操作的。到目前为止我们所有的构造器都创建了
空散列,但是我们没有理由让它们这么空着。比如,我们可以让构造器接受额外的参数,并且把
它们当作键/值。有关面向技术把这些称为“性质(Property)”,"属性(attribute)","存储器(accessor)"
"成员属性(member data)","实例数据(instance date)"或者"实例变量(instance variable)"
=cut
#假设一个Horse类有一些实例属性,如"name" 和 "color":
$steed = Horse->new(name => 'shadowfax', color => "white");
#如果该对象是用散列引用实现的,那么一旦引用者从参数列表删除,那么
#键/值对就可以插进散列:
sub new
{
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {@_}; #剩下的参数变成属性
bless($self,$class); #给予对象性质
return $self;
}
=pod
这回我们为该类的构造器使用一个名字叫new的方法,就这样可以使用那些C++程序
员相信这些都是正常的东西.不过perl可不这么认为"new"有任何特殊的地方;你可以
把构造器命名为任意的东西.任何碰巧创建和返回对象的方法都是实际意义上的构造
器。
=cut
sub new
{
my $invocant = shift;
my $class = ref($invocant) ||$invocant;
my $self = {
color => "bay",
legs => 4,
owner => undef,
@_, #覆盖以前的属性
};
return bless $self,$class;
}
$eg = Horse->new; #四腿栗马
$stallion = Horse->new(color => "black") #黑色的四腿栗马
=pod
把这个Horse构造器当作实例方法使用时,它将忽略它的调用者现在有的属性,
你可以设计第二个构造器,把它当作实例方法来调用,如果设计得合理,你就可以
使用来调用者的值作为新生成的对象的缺省值:
=cut
$steed = Horse->new(color => 'dun');
$foal = $steed->clone(owner => 'EquuGen Guild,Ltd');
sub clone
{
my $model = shift;
my $self = $model->new(%$model,@_);
return $self; #前面被->new bless过了
}
#[7] 类继承
=pod
当你调用一个方法的时候,如果perl在调用者的包里找不到这个子
例程,那么它就检查@ISA数组。
Perl是这样实现继承的:一包的@ISA数组里的每个元素都保存另一个包的名字,当
缺失方法的时候就搜索这些包。比如,下面的代码把Horse类变成Cirtter类的子类.
=cut
#我们必须用our声明@ISA,因为它必须是一个打包的变量,而不是my声明词
package Horse;
our @ISA = "Cirtter";
#当你调用了调用者的一个类型为classname的方法methodname时,
#Perl尝试六种不同的方法来找出所用的子例程
=pod
1.首先,Perl在自己的包里找一个叫classname::methodname的子例程,如果失败,
则进入继承,并进入步骤2.
2.第二步,Perl通过检查@classname::ISA中列出的所有的父包,检查从基类继承过
来的方法,看看有没有parent::methodname子例程.这种搜索从左向右,递归,深度
优先进行的.递归保证祖父类,曾祖父类,太祖父类等等类都进行搜索
3.如果仍然失败,Perl就搜索一个叫UNIVERSAL::methodname的子例程.
4.这时,perl放弃methodname然后开始查找AUTOLOAD.首先,它检查叫做class::AUTOL
OAD的子例程。
5.如果上面的步骤失败,perl则搜索的有在@classname::ISA中列出的parent包,寻找
任何parnet::AUTOLOAD子例程.这样搜索仍然是从左向右,递归,深度优先进行的。
6.最后,perl寻找一个叫UNIVERSAL::AUTOLOAD的例程。
=cut
#perl会在找到的第一个子例程处停止并调用该例程。如果没有找到,则产生一个异常:
'Can't locate object method "methname" via package "classname"';'
#通过@ISA继承
package Mule;
use base ("Hourse","donkey"); #声明一个父类
package Mule;
BEGIN {
our @ISA = qw(Hourse donkey);
require Hourse;
require donkey;
}
#访问被覆盖的方法
=pod
定义类方法时,该例程会覆盖任意基类中同名的方法.想象一下你有一个Mule
对象(它是人人Horse类和Donkey类派生出来的),而且你想调用你的对象的breed
方法.尽管父类有它们自己的breed方法,Mule类设计者通过给Mule类写自己的breed
方法覆盖了父类那些的breed方法。这就意味着下面交叉引用可能不能正常的工作:
=cut
$stallion = Hourse->new(gender => 'male');
$molly = Mule->new(gender => 'female');
$colt = $moly->breed($stallion);
#明确确保传递者的调用者:
$solt = $molly->Horse::Breed($stallion);
=pod
有时候,你希望一个派生类的方法表现得像基类中某些方法的包装.实际上
派生类的方法本身就可以调用基类的方法,在调用前或调用后增加自己的动作.
你可以这种表示法只用于演示指定从哪个类开始搜索.但是在使用覆盖方法大
多数情况下,你并不希望自己要知道或指定该执行哪个父类被覆盖的方法。
这就是SUPER伪类提供便利的地方.它令你能够调用一个覆盖了的基类的方法,
而不用指定哪个类定义了该方法。
=cut
#下面的子例程在当前包的@ISA中查找,而不要求指定特定的类:
package Mule;
our @ISA = qw(Horse Donkey);
sub kick
{
my $self = shift;
print "The mule kicks\n";
$self->SUPPER::kick(@_);
}
=pod
出现多重继承时,SUPPER并不是总是按你想的那样运行.它也像@ISA那样
遵循普通的继承机制的规则:从左向右,递归,深度优先.如果Horse和Donkey
都有一个speak方法,而你需要使用Donkey的方法,你不得不明确命名该
父类:
=cut
sub speak
{
my $self = shift;
print "The mule speak\n";
$self->Donkey::speak(@_);
}
perl对象
最新推荐文章于 2018-10-11 12:54:00 发布