Perl 类的定义
Perl的一个packag可以作为一个类使用,文件后缀名为.pm,并且把package里的函数当作类的方法来用。如:
package Person;
创建和使用对象
大多数程序使用类名作为构造函数,Perl 中可以使用任何名字。
你可以使用多种 Perl 的变量作为 Perl 的对象。大多数情况下我们会使用引用数组或哈希。
接下来我们为 Person 类创建一个构造函数,使用了 Perl 的哈希引用。如:
package Person; sub new { my $class = shift; my $self = { _firstName => shift, _lastName => shift, _ssn => shift, }; # 输出用户信息 print "名字:$self->{_firstName}\n"; print "姓氏:$self->{_lastName}\n"; print "编号:$self->{_ssn}\n"; bless $self, $class; return $self; }
接下来我们创建一个对象:
$object = new Person( "小明", "王", 23234345);
也可以使用如下方法创建对象:
$object = Person->new( "小明", "王", 23234345);
定义方法
Perl类的方法只是个Perl子程序而已,也即通常所说的成员函数。
Perl面向对象中Perl的方法定义不提供任何特别语法,但规定方法的第一个参数为对象或其被引用的包。
Perl 没有提供私有变量,但我们可以通过辅助的方式来管理对象数据。(比如通过方法中的第一个参数也就是对象或者引用的包的哈希来保存私有变量),如下所示:
sub setFirstName {
my ( $self, $firstName ) = @_;
$self->{_firstName} = $firstName if defined($firstName);
return $self->{_firstName};
}
修改Person.pm 如下:
#!/usr/bin/perl package Person; sub new { my $class = shift; my $self = { _firstName => shift, _lastName => shift, _ssn => shift, }; # 输出用户信息 print "名字:$self->{_firstName}\n"; print "姓氏:$self->{_lastName}\n"; print "编号:$self->{_ssn}\n"; bless $self, $class; return $self; } sub setFirstName { my ( $self, $firstName ) = @_; $self->{_firstName} = $firstName if defined($firstName); return $self->{_firstName}; } sub getFirstName { my( $self ) = @_; return $self->{_firstName}; } 1;
Perl继承
Perl 里 类方法通过@ISA(Perl内置数组)数组继承,这个数组里面包含其他包(类)的名字,变量的继承必须明确设定。
多继承就是这个@ISA数组包含多个类(包)名字。
通过@ISA只能继承方法,不能继承数据。但是EXPORT可以是方法,也可以是数据,参见文章“Perl中神奇的@EXPORT”
接下来我们创建一个 Employee 类继承 Person 类。
#!/usr/bin/perl package Employee; use Person; use strict; our @ISA = qw(Person); # 从 Person 继承 # 重写构造函数 sub new { my ($class) = @_; # 调用父类的构造函数 my $self = $class->SUPER::new( $_[1], $_[2], $_[3] ); # 添加更多属性 $self->{_id} = undef; $self->{_title} = undef; bless $self, $class; return $self; } # 重写方法 sub getFirstName { my( $self ) = @_; # 这是子类函数 print "这是子类函数\n"; return $self->{_firstName}; } # 添加方法 sub setLastName{ my ( $self, $lastName ) = @_; $self->{_lastName} = $lastName if defined($lastName); return $self->{_lastName}; } sub getLastName { my( $self ) = @_; return $self->{_lastName}; } 1;
当在类中找不到某个实例方法时,它就会检查该类的 @ISA 是否被初始化。如果已经初始化了,它检查其中的某个模块是否支持这个“缺少”的函数。如果它按照深度优先的层次结构搜索 @ISA 数组并且发现同名的方法,它会调用第一个被发现的同名方法并将控制权交给它。我们利用 Perl 语言的这个特性实现了继承。
如上所示继承后子类增加了自己的方法,并且在子类中对方法进行了重写。
这里我们注意到在子类的构造函数中我们通过SUPER调用了基类的构造函数:
my $self = $class->SUPER::new( $_[1], $_[2], $_[3] );
我们也可以直接使用基类的类名来调用:
my $self = Person::new( $_[1], $_[2], $_[3] );
如果只知道父类的方法名,而不知道父类名,我们可以使用伪类保留字SUPER::
总结
通过在子类中use 基类,并将基类名放在内置数组@ISA中即可实现继承:
#!/usr/bin/perl package Employee; use Person; use strict; our @ISA = qw(Person); # 从 Person 继承
Perl 中的bless
从前面的代码中可以看到Perl类的构造函数使用了bless 函数。下面介绍其由来。
Perl不像其他的面向对象语言具有变量修饰符(public, private ,final...). 类属性只是包中的全局变量,而类方法则是不依赖于任何特定实例的普通子例程。如下为类方法和类属性的例子:
package Person; my $person_number = 0; sub new { my $self = {}; shift; my ($name, $age) = @_; $self->{name} = $name; $self->{age} = $age; $person_number++; bless ($self); return $self; } sub calculate_person_number { return $person_number; } my $object_personA = Person->new ( “ David ” , 27); my $object_personB = Person::new ( “ Tonny ” , 27); my $person_number = Person::calculate_person_number (); print “ We have ” . $person_number . “ persons in all. \n ” ;
因此,不能将实例的变量直接放在整个package中(否则将成为类变量)。我们只能这么做:
package Person; sub new { my ($name, $age) = @_; my $r_object = { “ name ” => $name, “ age ” => $age } return $r_object; } my $personA = Person->new ( “ Tommy ” , 22 ); my $personB = Person->new ( “ Jerry ” , 30 ); print “ Person A ’ s name: ” . $personA->{name} . “ age: ” . $personA->{age} . ” .\n ” ; print “ Person B ’ s name: ” . $personB->{name} . “ age: ” . $personB->{age} . ” .\n ” ;
那么问题来了:Perl 的编译器并不知道new 函数所返回的指向匿名哈希表的引用属于哪个类(模块)。这样的话,如果要使用类中的实例方法,只能直接标出方法所属于的类(模块)的名字,并将引用作为方法的第一个参数传递给它,如下所示:
package Person; … sub change_name { my ($self, $new_name) = @_; $self->{name} = $new_name; } my $object_person = Person->new ( “ Tommy ” , 22); print “ Person ’ s name: ” . $object_person->{name} . “ .\n ” ; Person::change_name ($object_person, “ Tonny ” ); print “ Person ’ s new name: ” . $object_person->{name} . “ .\n ” ;
对于这个问题,Perl 中的 bless 函数提供了一个解决问题的桥梁。 bless 以一个普通的指向数据结构的引用为参数,它将会把那个数据结构(注意:此处不是引用本身)标记为属于某个特定的包,这样就赋予了这个匿名哈希表的引用以多态的能力。同时,我们使用箭头记号来直接调用那些实例方法,如下所示:
package Person my $person_number = 0; sub new { my $self = {}; shift; my ($name, $age) = @_; $self->{name} = $name; $self->{age} = $age; bless ($self); return $self; } sub change_name { my $self = shift; my $name = shift; $self->{name} = $name; } sub getName { my $self = shift; return $self->{name}; } sub calculate_person_number { return $person_number; } my $object_person = Person->new ( “ David ” , 27); print “ Name: “ . $object_person->{name} . “ \n ” ; $object_person->change_name ( “ Tony ” ); print “ Name: “ . $object_person->{name} . “ \n ” ; my $object_personA = Person->new ( “ David ” , 27); my $object_personB = Person::new ( “ Tonny ” , 27); my $person_number = Person::calculate_person_number (); print “ We have ” . $person_number . “ persons in all. \n ” ;
如上代码 中的“ bless ($self) ”,将指向一个匿名哈希表的引用标记为属于当前包,也就是 package Person 。
所以,当 Perl 看到“ $object_person->change_name ($name) ”时,它会决定 $object_person 属于 package Person 。 Perl 就会如下所示地调用这个函数,“ Person::change_name ($object_person, $name) "。
换而言之,如果使用箭头的方式调用一个函数,箭头左边的那个对象将作为相应子例程的第一个参数。 Perl 的实例方法的本质其实就是一个第一个参数碰巧为对象引用的普通子例程。
与实例方法不同,我们使用 Person::calculate_person_number () 的形式来调用类方法。这样的话,指向匿名哈希表的引用将不会作为第一个调用参数传入。
(从这里也可以看出::和->使用的区别,一个是类方法调用,一个是实例方法调用)
一般的bless是使用两个参数的,如下:
sub new { my ($class) = @_; # 调用父类的构造函数 my $self = $class->SUPER::new( $_[1], $_[2], $_[3] ); # 添加更多属性 $self->{_id} = undef; $self->{_title} = undef; bless $self, $class; return $self; }
当第二个参数省略时默认为当前包。
参考文献:
http://www.cnblogs.com/A-Song/archive/2012/04/12/2443541.html
http://blog.sina.com.cn/s/blog_6151984a0100eq6e.html
http://www.runoob.com/perl/perl-object-oriented.html