设为首页 收藏本站
查看: 1386|回复: 0

[经验分享] Perl5的OOP学习笔记

[复制链接]

尚未签到

发表于 2018-9-1 09:14:07 | 显示全部楼层 |阅读模式
  在Perl排名持续下降的情况下学Perl,似乎是有点不明智。但是,工作需要,不得不学啊。再说,Perl现在在测试领域还是用得非常多的。Phython虽然也在测试领域开始活跃起来,不过我始终还是不太喜欢Phython的语法。
  在学习了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 {....}
  

  Perl中,name::jamesfancy::MyClass,应对的是/name/jamesfancy/MyClass.pm,源代码中只有一句package就说明了
  


  • 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函数的。
  


  • # test.pl
  • package MyClass;

  • sub new {
  •     my $this = {};
  •     bless($this);
  • }

  • package main;

  • my $obj1 = MyClass::new();
  • my $obj2 = MyClass->new();
  • my $obj3 = new MyClass();

  • print(join("/n", ref($obj1), ref($obj2), ref($obj3)));

  • __END__
  • MyClass
  • MyClass
  • MyClass
  

  注意上面new MyClass()的效果和MyClass->new()效果是一样的。这里new不是关键字,而是函数名。同理,如果有一个foo成员函数的话,也可以foo MyClass(args),它实际上是MyClass::foo(MyClass, args);
  话说回来,如果需要初始化对象数据又该如何呢?前面说过,对象数据保存在引用的数据自身,所以我们通常是把一个Hash引用bless成对象。所以我们经常会看到这样调用new:
  


  • my $obj = MyClass->new('key1' => 'value1', 'key2' => 'value2');
  

  或者
  


  • my $obj = MyClass->new({'key1' => 'value1', 'key2' => 'value2'});
  

  两种调用方式的区别在于new函数中的处理不同,因为前者传入的是一个Hash实体,而后者传入的是一个Hash引用。为了兼容这两种情况,new函数通常会像下面程序中的写法:
  


  • # test.pl
  • package MyClass;

  • sub new {
  •     my $class = shift();
  •     my $this = ref(@_[0]) ? @_[0] : {@_};
  •     bless($this);
  • }

  • package main;
  • use Data::Dumper;

  • my $obj1 = MyClass->new('name' => 'James Fancy', 'age' => 30);
  • my $obj2 = MyClass->new({'name' => 'James Fancy', 'age' => 30});

  • print(Dumper($obj1));
  • print(Dumper($obj2));

  • __END__
  • $VAR1 = bless( {
  •                  'name' => 'James Fancy',
  •                  'age' => 30
  •                }, 'MyClass' );
  • $VAR1 = bless( {
  •                  'name' => 'James Fancy',
  •                  'age' => 30
  •                }, 'MyClass' );
  

访问对象数据
  既然通常是Hash引用被bless成对象,那就只说这种情况。
  既然是Hash引用,所以访问数据最简单的办法就跟访问Hash引用一样。比如
  


  • $obj->{'name'} = "You Name";
  • my $name = $obj->{'name'};
  

  如果想少写点花括号,可以通过定义setter/getter的办法来解决。因为getter和setter可以根据有没参数来区分,所以合并在一个函数中成为可能,比如下面的name函数
  


  • # test.pl
  • package MyClass;

  • sub new {
  •     my $class = shift();
  •     my $this = ref(@_[0]) ? @_[0] : {@_};
  •     bless($this);
  • }

  • sub name {
  •     my $this = shift();
  •     if (@_[0]) {
  •         $this->{'name'} = @_[0];
  •     }
  •     return $this->{'name'};
  • }

  • package main;

  • my $obj = MyClass->new('name' => 'James Fancy');
  • print($obj->name, "/n");
  • print($obj->name("New Name"), "/n");

  • __END__
  • James Fancy
  • New Name
  

  使用setter/getter的确可以使程序看起来简洁不少。但是对对象中的每个数据写一个getter/setter,还是很累人的,于是,AUTOLOAD函数就被抬出来了,看看下面的程序
  


  • package MyClass;

  • sub new {
  •     my $class = shift();
  •     my $this = ref(@_[0]) ? @_[0] : {@_};
  •     bless($this, $class);
  • }

  • sub AUTOLOAD {
  •     my $this = $_[0];
  •     if (!ref($this)) {
  •         return;
  •     }

  •     my $name = $AUTOLOAD;

  •     if (defined($name)) {
  •         $name =~ s/.*:://;
  •     } else {
  •         return;
  •     }

  •     my $class = ref($this);
  •     if (defined($this->{$name}) || @_) {
  •         no strict 'refs';
  •         *{"${class}::$name"} = sub {
  •             my $this = shift();
  •             $this->{$name} = shift() if (@_);

  •             # make a property in hash reference type to HashObject object.
  •             if (ref($this->{$name}) eq 'HASH') {
  •                 bless($this->{$name}, $class);
  •             }

  •             return $this->{$name};
  •         };

  •         goto &$name;
  •     }
  • }

  • package main;

  • my $obj = MyClass->new('name' => 'James Fancy');
  • $obj->more1({'key', 'value of more1->key'});
  • print($obj->name, "/n");
  • print($obj->more1->key, "/n");
  • print($obj->more2({})->key("value of more2->key"), "/n");

  • __END__
  • James Fancy
  • value of more1->key
  • value of more2->key
  

  这样调用起来是不是方便多了?不过AUTOLOAD写起来很累人的。如果你只需要一个数据对象,网上有个Hash::AsObject的类很好用,用法和上面的最后一个示例差不多。

继承
  我的确是对继承这个方面没怎么研究。不过简单的继承大概就是用use base语句引入基类而已,比如
  


  • package Parent;

  • sub test1 {
  •     print("Parnet::test1/n");
  • }

  • sub test {
  •     print("Parent::test/n");
  • }

  • package Sub;
  • use base Parent;

  • sub test {
  •     print("Sub::test/n");
  • }

  • sub test2 {
  •     $_[0]->Parent::test();
  • }

  • package main;

  • my $obj = bless({}, *Sub);
  • $obj->test();
  • $obj->test1();
  • $obj->test2();

  • __END__
  • Sub::test
  • Parnet::test1
  • Parent::test
  

参考资料
  东南大学出版社出版,O'Reilly的《精通Perl(影印版)》,brian d foy著
  
Perl version 5.10.0 documentation,http://perldoc.perl.org/
  
Hash::AsObject源码,来自http://search.cpan.org/~nkuitse/Hash-AsObject-0.11/lib/Hash/AsObject.pm



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-560982-1-1.html 上篇帖子: perl获取当前用户名 下篇帖子: bugzilla更新出现perl模块无法安装问题解决办法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表