Perl5 OOP学习笔记第1/2页
程序员文章站
2022-04-10 22:26:39
在学习了perl的基本语法之后,学习perl的oop,略有心得。不知道perl各个版本之间oop是否有区别,但是我是学习的perl5,所以在标题上将版本号也写出来了。因为了...
在学习了perl的基本语法之后,学习perl的oop,略有心得。不知道perl各个版本之间oop是否有区别,但是我是学习的perl5,所以在标题上将版本号也写出来了。因为了解到php4和php5的oop部分就有不小的差别,所以有此担心。
学习perl的oop,最关键的两件事情就是package和bless。只要把这两个东西搞清楚也就学会大一半了。
perl的package
感觉perl的package和java还真有点相似。java的package是以classpath中的目录为根,按目录定义和搜索分级包名。perl也类似,是以@inc数组中的目录为根,按目录搜索分级包名。不过有一点不同,perl的package定义貌似不需要与目录结构对应。具体是什么样的规则我没有去研究,因为按目录结构定义package是个好习惯。
相较于java,perl的package还有一点很有意思。java的每层package对应一个目录,而最后是一个class文件对应到类名。perl却简化了,package直接就把目录和文件名都引用了进去。比如
java中,name.jamesfancy.myclass,对应的是/name/jamesfancy/myclass.class,源代码中则分成两句来写
package name.jamesfancy;
class myclass {....}
package name.jamesfancy;
class myclass {....}
perl中,name::jamesfancy::myclass,应对的是/name/jamesfancy/myclass.pm,源代码中只有一句package就说明了
package name::jamesfancy::myclass;
package name::jamesfancy::myclass;
至于package中的内容,也就是变量和子程序,至于区别,稍后再说。
bless函数
bless是用来把一个类绑定到引用类型变量的函数。很奇怪perl为什么要用这个单词,不过没关系,我们可以把它想像得形象一点:就像游戏里牧师通过祝福技能为某人加上buff一样,bless把一个类绑定到某个引用类型的变量,从此这个变量就受到了祝福,拥有了这个类中的变量和子程序。
bless的用法通常是:bless($引用变量, 类名);
引用变量貌似可以是任何引用类型的变量,我尝试过scalar,array和hash的引用,都能成功。在bless之外,这个引用变量就可以被称之为对象了,当然它仍然是个引用,是对象的引用。
有一点还需要注意,虽然这个对象拥有了类的变量和子程序,但我们应该把它拥有的类的变量和子程序都看成是静态的,换句话说,就是类的成员。在这一点上,子程序的处理会比较特殊一点,但至少类的变量,也就是包变量,是不属于对象的。因此,所有对象的数据都保存在对象引用的原始数据中。既然大家都习惯对象数据以键值对的方式保存,所以通常情况下,bless的引用变量,都是hash的引用了。
很抽象么?举个例子。如果对oop的成员函数还不够了解,那就只看下面示例中每个类的test函数中第一句以后的内容不好。
# test.pl
package testscalar;
sub test {
my $this = shift();
print("\nin testscalar::test()\n");
print("scalar:\n ${$this}\n");
}
package testarray;
sub test {
my $this = shift();
print("\nin testarray::test()\n");
print("array:\n");
foreach my $item (@{$this}) {
print(" $item\n");
}
}
package testhash;
sub test {
my $this = shift();
print("\nin testhash::test()\n");
print("hash:\n");
while (my ($key, $value) = each %{$this}) {
printf(" %-4s = %s\n", $key, $value);
}
}
package main;
my $name = "james fancy";
my $objscalar = \$name;
my $objarray = ['james', 'fancy', 'jenny'];
my $objhash = {'name' => 'james', 'age' => 30};
bless($objscalar, 'testscalar');
bless($objarray, 'testarray');
bless($objhash, 'testhash');
$objscalar->test();
$objarray->test();
$objhash->test();
__end__
in testscalar::test()
scalar:
james fancy
in testarray::test()
array:
james
fancy
jenny
in testhash::test()
hash:
name = james
age = 30
从上面的示例中可以看到,分别将3种类型的引用转变为对象。之所以要把类写成3个而非1个,主要是为了在test里输出不同类型的数据。
my $this = shift();
当然,这里的$this只是一个局部变量,而不是关键字,你也可以用别的名称来代替它。比如很多人就喜欢用$self,或者$me等。
假如,对于一个成员函数,分别用类和对象来对它进行调用,会有什么不一样呢?再看一个示例:
# test.pl
package myclass;
sub test {
my ($this, @args) = @_;
print('-' x 40, "\n");
print("\$this is [$this], ref of \$this is [", ref($this), "]\n");
print("args: [@args]\n");
}
package main;
$obj = {};
bless($obj, 'myclass');
myclass->test("myclass->test(...)");
$obj->test("\$obj->test(...)");
__end__
----------------------------------------
$this is [myclass], ref of $this is []
args: [myclass->test(...)]
----------------------------------------
$this is [myclass=hash(0x178a44)], ref of $this is [myclass]
args: [$obj->test(...)]
从结果可以看出来,不管哪种方法调用,第一个参数都是perl偷偷传递进去的。如果是类调用,则第一个参数是该类。如果是对象调用,第一个参数是该对象。因此,只需要将ref($this)的结果和类名进行比较就清楚是哪种调用了。所以,一个容错性较好的成员函数,一开始要判断传入的第一个参数,比如
sub foo {
my $this = shift();
return unless ($this ne 'myclass');
# 其它语句
}
这里还有一个疑问:既然package中定义的子程序都是成员函数,那不是类的package和是类的package有啥区别?它们在结构上没有一点区别,唯一的区别在处理中。在调用子程序的时候,perl不会硬塞一个类或者对象在参数列表的最前面,但调用成员函数的时候会,所以区别是根据你的调用方式来区分的。
调用对象成员还好说,$obj->foo()就好,但是调用类成员的时候,怎么知道是调用的类成员还是包中的子程序呢?那就要看是通过“->”还是“::”来调用的了。下面的例子可以帮助理解:
# test.pl
package myclass;
use data::dumper;
sub test {
print('-' x 40, "\n");
print(dumper(@_));
}
package main;
myclass->test("myclass->test(...)");
myclass::test("myclass::test(...)");
__end__
----------------------------------------
$var1 = 'myclass';
$var2 = 'myclass->test(...)';
----------------------------------------
$var1 = 'myclass::test(...)';
很明显,通过“::”调用的子程序没有被perl塞入一个引用类的参数。
构造函数
perl的oop没有指定专门的构造函数,所以你可以把任何一个子程序当作构造函数,当然,重要的是其中的内容。既然脚本通常不是写给自己一个人看的,所以还是按照大家的习惯,把构造函数取名为new吧。按照多数oop语言的习惯,new函数通常返回一个对象或其引用、指针。所以在perl中,这个new函数要返回一个对象引用,理所当然地,把bless动作包含在new函数中是个好习惯。那么一个简单的new函数看起来就像这样:
sub new {
my $this = {};
bless($this);
}
这个new函数中产生了一个hash引用,bless它,并返回它。如果你疑惑为什么这里没有看到return语句,那么建议你去看看关于子程序中返回值的资料,顺便查一下bless函数的说明。来看看完整的程序了解一下是怎么使用new函数的。
学习perl的oop,最关键的两件事情就是package和bless。只要把这两个东西搞清楚也就学会大一半了。
perl的package
感觉perl的package和java还真有点相似。java的package是以classpath中的目录为根,按目录定义和搜索分级包名。perl也类似,是以@inc数组中的目录为根,按目录搜索分级包名。不过有一点不同,perl的package定义貌似不需要与目录结构对应。具体是什么样的规则我没有去研究,因为按目录结构定义package是个好习惯。
相较于java,perl的package还有一点很有意思。java的每层package对应一个目录,而最后是一个class文件对应到类名。perl却简化了,package直接就把目录和文件名都引用了进去。比如
java中,name.jamesfancy.myclass,对应的是/name/jamesfancy/myclass.class,源代码中则分成两句来写
复制代码 代码如下:
package name.jamesfancy;
class myclass {....}
package name.jamesfancy;
class myclass {....}
perl中,name::jamesfancy::myclass,应对的是/name/jamesfancy/myclass.pm,源代码中只有一句package就说明了
复制代码 代码如下:
package name::jamesfancy::myclass;
package name::jamesfancy::myclass;
至于package中的内容,也就是变量和子程序,至于区别,稍后再说。
bless函数
bless是用来把一个类绑定到引用类型变量的函数。很奇怪perl为什么要用这个单词,不过没关系,我们可以把它想像得形象一点:就像游戏里牧师通过祝福技能为某人加上buff一样,bless把一个类绑定到某个引用类型的变量,从此这个变量就受到了祝福,拥有了这个类中的变量和子程序。
bless的用法通常是:bless($引用变量, 类名);
引用变量貌似可以是任何引用类型的变量,我尝试过scalar,array和hash的引用,都能成功。在bless之外,这个引用变量就可以被称之为对象了,当然它仍然是个引用,是对象的引用。
有一点还需要注意,虽然这个对象拥有了类的变量和子程序,但我们应该把它拥有的类的变量和子程序都看成是静态的,换句话说,就是类的成员。在这一点上,子程序的处理会比较特殊一点,但至少类的变量,也就是包变量,是不属于对象的。因此,所有对象的数据都保存在对象引用的原始数据中。既然大家都习惯对象数据以键值对的方式保存,所以通常情况下,bless的引用变量,都是hash的引用了。
很抽象么?举个例子。如果对oop的成员函数还不够了解,那就只看下面示例中每个类的test函数中第一句以后的内容不好。
复制代码 代码如下:
# test.pl
package testscalar;
sub test {
my $this = shift();
print("\nin testscalar::test()\n");
print("scalar:\n ${$this}\n");
}
package testarray;
sub test {
my $this = shift();
print("\nin testarray::test()\n");
print("array:\n");
foreach my $item (@{$this}) {
print(" $item\n");
}
}
package testhash;
sub test {
my $this = shift();
print("\nin testhash::test()\n");
print("hash:\n");
while (my ($key, $value) = each %{$this}) {
printf(" %-4s = %s\n", $key, $value);
}
}
package main;
my $name = "james fancy";
my $objscalar = \$name;
my $objarray = ['james', 'fancy', 'jenny'];
my $objhash = {'name' => 'james', 'age' => 30};
bless($objscalar, 'testscalar');
bless($objarray, 'testarray');
bless($objhash, 'testhash');
$objscalar->test();
$objarray->test();
$objhash->test();
__end__
in testscalar::test()
scalar:
james fancy
in testarray::test()
array:
james
fancy
jenny
in testhash::test()
hash:
name = james
age = 30
从上面的示例中可以看到,分别将3种类型的引用转变为对象。之所以要把类写成3个而非1个,主要是为了在test里输出不同类型的数据。
类和对象的成员函数
成员函数就是在package中定义的子程序。成员函数是没有静态和非静态之分的,但我宁愿大家都把它看作是静态函数,因为虽然它即可以当作类成员函数来调用,也可以当用对象成员函数来调用,但在当作对象成员函数来调用的时候,perl偷偷的传入了对象引用。这也解释了为什么通常成员函数里的第一句话往往是
复制代码 代码如下:
my $this = shift();
当然,这里的$this只是一个局部变量,而不是关键字,你也可以用别的名称来代替它。比如很多人就喜欢用$self,或者$me等。
假如,对于一个成员函数,分别用类和对象来对它进行调用,会有什么不一样呢?再看一个示例:
复制代码 代码如下:
# test.pl
package myclass;
sub test {
my ($this, @args) = @_;
print('-' x 40, "\n");
print("\$this is [$this], ref of \$this is [", ref($this), "]\n");
print("args: [@args]\n");
}
package main;
$obj = {};
bless($obj, 'myclass');
myclass->test("myclass->test(...)");
$obj->test("\$obj->test(...)");
__end__
----------------------------------------
$this is [myclass], ref of $this is []
args: [myclass->test(...)]
----------------------------------------
$this is [myclass=hash(0x178a44)], ref of $this is [myclass]
args: [$obj->test(...)]
从结果可以看出来,不管哪种方法调用,第一个参数都是perl偷偷传递进去的。如果是类调用,则第一个参数是该类。如果是对象调用,第一个参数是该对象。因此,只需要将ref($this)的结果和类名进行比较就清楚是哪种调用了。所以,一个容错性较好的成员函数,一开始要判断传入的第一个参数,比如
复制代码 代码如下:
sub foo {
my $this = shift();
return unless ($this ne 'myclass');
# 其它语句
}
这里还有一个疑问:既然package中定义的子程序都是成员函数,那不是类的package和是类的package有啥区别?它们在结构上没有一点区别,唯一的区别在处理中。在调用子程序的时候,perl不会硬塞一个类或者对象在参数列表的最前面,但调用成员函数的时候会,所以区别是根据你的调用方式来区分的。
调用对象成员还好说,$obj->foo()就好,但是调用类成员的时候,怎么知道是调用的类成员还是包中的子程序呢?那就要看是通过“->”还是“::”来调用的了。下面的例子可以帮助理解:
复制代码 代码如下:
# test.pl
package myclass;
use data::dumper;
sub test {
print('-' x 40, "\n");
print(dumper(@_));
}
package main;
myclass->test("myclass->test(...)");
myclass::test("myclass::test(...)");
__end__
----------------------------------------
$var1 = 'myclass';
$var2 = 'myclass->test(...)';
----------------------------------------
$var1 = 'myclass::test(...)';
很明显,通过“::”调用的子程序没有被perl塞入一个引用类的参数。
构造函数
perl的oop没有指定专门的构造函数,所以你可以把任何一个子程序当作构造函数,当然,重要的是其中的内容。既然脚本通常不是写给自己一个人看的,所以还是按照大家的习惯,把构造函数取名为new吧。按照多数oop语言的习惯,new函数通常返回一个对象或其引用、指针。所以在perl中,这个new函数要返回一个对象引用,理所当然地,把bless动作包含在new函数中是个好习惯。那么一个简单的new函数看起来就像这样:
复制代码 代码如下:
sub new {
my $this = {};
bless($this);
}
这个new函数中产生了一个hash引用,bless它,并返回它。如果你疑惑为什么这里没有看到return语句,那么建议你去看看关于子程序中返回值的资料,顺便查一下bless函数的说明。来看看完整的程序了解一下是怎么使用new函数的。
1