面向对象的Perl 之二

来源:互联网 发布:数据分析 英文 编辑:程序博客网 时间:2024/06/08 07:02
14.2.8 多态性和动态绑定
Webster 辞典把多态性(polymorphism)定义为:
polymorphism: n. 1. the state or condition of being polymorphous。
为了说的更清楚些,下面是另一个定义:
具有多种形态或采取多种行为方式的能力... 同一个操作在不同的类中表现为不同的行为。
多态性拥有很多种不同的描述方式,它早已隐含在了OO 语境中。
在Perl 中,它的意思是可以使用同一个名称在不同的类中提供同一种方法,
而在调用方法时,它会自动进行正确的选择;
换而言之,当通过对象引用调用方法时,它将进入对象所属的类中。
下面这个示例14.10 说明了多态性的特点。
这里用到了两个模块Cat.pm 和Dog.pm,
每个模块都含有三个同名函数:new、set_attributes 和get_attributes。
示例14.10 中的驱动程序(或用户程序)将使用这两个模块。
当调用各自的类构造函数时,便能返回指向cat 或dog 对象的引用。
当使用对象引用来访问方法时,Perl 会知道调用的子例程及其所属的类,即使方法出现重名也不要紧。
Perl确定了待调用对象所属的类,并在该类(包)中搜寻调用的方法。
这里调用正确方法的能力就展示出所谓的多态性。
动态绑定又称为运行时绑定(runtime binding),它能允许程序推迟调用正确的方法,直到程序开始执行。
它与多态性一起负责将正确的方法绑定到关联的类上,而不必通过if 语句来决定需要调用的方法。
这样就提供了很大的灵活性,并且这对继承机制的正确性也是非常必要的。
若要利用多态性和运行时绑定,就必须使用面向对象的语法,而不是传统的:: 语法。
例如,倘若由两个类Director 和Rifleman,它们均含有方法shoot。
用户可以把它写成$object->shoot,而Perl 会知道对象所属的类。它会在编译时确定正确的类。
通过这种方式,Director 就不会在分配角色时射出子弹,而Rifleman 也不会尝试拍摄有关步枪射击的电影。
读者还可以添加其他的类,
譬如含有不同shoot 方法的BasketballPlayer 类,并能确保对于该类也能调用正确的方法。
如果没有运行时绑定和多态机制的话,程序就只能根据某种条件判断的结果才能确定该用哪个正确的类,
譬如:
if ( ref($object1) eq "Director") {
     Director::shoot($object1);
elsif ( ref($object2) eq "Rifleman" ){
     Rifleman::shoot($object2);
else{
     BasketballPlayer::shoot($object3);
}
在面向对象的语法中,Perl 会隐式地将指向对象的一个引用作为参数传递给方法。
由于Perl 会把对象引用传递当作第一个参数传递给访问方法,并且该对象也已经归入到合适的类里,
因此Perl能够实现多态性,并作出正确的选择!
假定在示例14.10 中创建的$object1 对象属于Director 类,
$object2 对象属于Rifleman 类,而$object3 则属于BasketballPlayer 类。
$object1->shoot;evaluates to Director::shoot($object1);
$object2->shoot;evaluates to Rifleman::shoot($object2);
$object3->shoot;evaluates to BasketballPlayer::shoot($object3)


示例14.10
( File: Cat.pm)
1 package Cat;
2 sub new{             # Constructor
      my $class=shift;
      my $dptr={};
      bless($dptr, $class);
  }
3 sub set_attributes{  # Access Methods
      my $self= shift;
4     $self->{"Name"}="Sylvester";
      $self->{"Type"}="Siamese"; 
      $self->{"Sex"}="Male";
  }
5 sub get_attributes{
      my $self = shift;
      print "-" x 20, "\n";
      print "Stats for the Cat\n";
      print "-" x 20, "\n";
      while(($key,$value)=each( %$self)){
        print "$key is $value. \n";
   }
   print "-" x 20, "\n";
1;
--------------------------------------------------------------------


(File: Dog.pm)
6 package Dog;
7 sub new{       # Constructor
      my $class=shift;
      my $dptr={};
      bless($dptr, $class);
  }
8 sub set_attributes{
      my $self= shift;
9     my($name, $owner, $breed)=@_;
10    $self->{"Name"}="$name";
      $self->{"Owner"}="$owner";
      $self->{"Breed"}="$breed";
   }
11 sub get_attributes{
      my $self = shift;
      print "x" x 20, "\n";
      print "All about $self->{Name}\n";
      while(($key,$value)= each( %$self)){
        print "$key is $value.\n";
      }
      print "x" x 20, "\n";
    }
1;
--------------------------------------------------------------------


解释
1. 这是模块Cat.pm 中对一个名叫Cat 的类的包声明。
2. Cat 类的构造函数方法名为new。本行会把一个Cat 对象归入到类中。
3. 访问方法set_attributes,定义Cat 对象的数据属性。
4. 使用对象指针$ref 给Cat 对象赋予一个键/ 值对,以便指定它的名字。
5. 使用另一个访问方法get_attributes 显示Cat 对象。
6. 在另一个文件Dog.pm 中,对一个名叫Dog 的类提供包声明。
7. 与Cat 类一样,Dog 类也有一个名叫new 的构造函数。本行会把一个Dog 对象归入到类中。
8. 访问方法set_attributes,定义Dog 对象的数据属性。
9. 从驱动程序中传递Dog 对象的属性,并赋值给@_ 数组。
10. 使用对象指针$ref 给Dog 对象赋予一个键/ 值对,以便指定它的名字。
11. 与Cat 类一样,使用另一个访问方法get_attributes 显示Dog 对象内容。


(The Script: driver program for Example 14.10)
#!/bin/perl
1 use Cat; # Use the Cat.pm module
2 use Dog; # Use the Dog.pm module
3 my $dogref = Dog->new; # Polymorphism
4 my $catref= Cat->new;
5 $dogref->set_attributes("Rover", "Mr. Jones", "Mutt");
6 $catref->set_attributes; # Polymorphism
7 $dogref->get_attributes;
8 $catref->get_attributes;


(Output)
xxxxxxxxxxxxxxxxxxxx
All about Rover
Owner is Mr. Jones.
Breed is Mutt.
Name is Rover.
xxxxxxxxxxxxxxxxxxxx
--------------------
Stats for the Cat
--------------------
Sex is Male.
Type is Siamese.
Owner is Mrs. Black.
Name is Sylvester.
--------------------
解释
1. use 指令负责加载Cat.pm 模块。
2. use 指令加载Dog.pm 模块。现在程序便可访问这两个类了。
3. 调用new 构造函数。
   其第一个参数是类名Dog,Perl 会将其翻译为Dog::new(Dog)。返回的是指向Dog 对象的引用。
   Perl 知道这个引用是属于Dog 类的,因为已经在构造函数中对它完成了归入(bless)处理。
   创建Dog 对象的实例。
4. 调用构造函数方法new,
   并把类名作为第一个参数传递给它。返回指向Cat 对象的引用。
   Perl会将方法调用翻译为Cat::new(Cat)。上述两个类都用到了new 函数,
   但由于Perl 知道该方法各自所属的类,因此始终能够调用正确的new 函数。这就是多态性的一个例子。
5. 有了指向对象的引用后,通过该引用调用访问方法(实例方法)。
   Perl 会把 $dogref->set_attributes
   翻译为    Dog::set_attributes( $dogref, "Rover", "Mr. Jones", "Mutt" )。
6. 这一次,调用cat 对象的set_attributes 方法。Cat 类会设置cat 的属性。
7. 调用get_attributes 方法显示Cat 类的数据属性。
8. 调用get_attributes 方法显示Dog 类的数据属性。


:: 和-> 标记。箭头-> 语法一般用在拥有多态性、动态绑定和继承行为的面向对象程序中。这
里也可以使用:: 语法,但后者更不灵活,而且如果不使用条件语句的话还会出问题。下面的示例中
展示了:: 语法的缺点。如果使用面向对象的语法,就不会出现这些问题了。


示例14.12
# The Cat class
package Cat;
sub new{                # The Cat's constructor
  my $class = shift;
  my $ref = {};
  return bless ($ref, $class);
}


sub set_attributes{     # Giving the Cat some attributes,
                        # a name and a voice
  my $self = shift;
  $self->{"Name"} = "Sylvester";
  $self->{"Talk"}= "Meow purrrrrr.... ";
}


sub speak { # Retrieving the Cat's attributes
  my $self = shift;
  print "$self->{Talk} I'm the cat called $self->{Name}.\n";
}
1;
---------------------------------------------------------------------


# The Dog class
package Dog; # The Dog's Constructor
sub new{
  my $class = shift;
  my $ref = {};
  return bless ($ref, $class);
}


sub set_attributes{ # Giving the Dog some attributes
  my $self = shift;
  $self->{"Name"} = "Lassie";
  $self->{"Talk"}= "Bow Wow, woof woof.... ";
}


sub speak { # Retrieving the Dog's attributes
  my $self = shift;
  print "$self->{'Talk'} I'm the dog called $self->{'Name'}.\n";
}
1;
---------------------------------------------------------------------


#!/bin/perl
# User Program
# This example demonstrates why to use the object-oriented
# syntax rather than the colon-colon syntax when passing
# arguments to methods.
use Cat;
use Dog;
$mydog = new Dog; # Calling the Dog's constructor
$mycat = new Cat; # Calling the Cat's constructor
$mydog->set_attributes; # Calling the Dog's access methods
$mycat->set_attributes; # Calling the Cat's access methods
2 $mycat->speak;
3 print "\nNow we make a mistake in passing arguments.\n\n";
4 Cat::speak($mydog); # Perl goes to the Cat class to find the
                      # method, even though attributes have been
                      # set for the dog!


(Output)
1 Bow Wow, woof woof.... I'm the dog called Lassie.
2 Meow purrrrrr.... I'm the cat called Sylvester.
3 Now we make a mistake in passing arguments.
4 Bow Wow, woof woof.... I'm the cat called Lassie.


解释
1. 以面向对象方式调用speak 方法,保证了Perl 能将该方法与归属类绑定在一起。
   将指向Dog 对象的引用作为第一个参数传递给speak 方法。Perl 会把它翻译为Dog::speak($mydog)。
   Perl 会在编译时确定使用的类,因此在调用speak 方法时能把它绑定到正确的类上面去。
2. 将指向Cat 对象的引用作为第一个参数传递给speak 方法。Perl 会把它翻译为Cat::speak($mycat)。
3. 下面的代码会把dog 引用传递给Cat 包。如果使用面向对象的方法,则不会出现这种错误,
   因为Perl 始终能确定方法所属的类,并调用正确的方法。该特性称为多态性或运行时绑定。
4. 通过使用:: 标记,Perl 会在运行时识别正确的类,并使用Cat 类中的speak 方法,即传送的
   是指向Dog 对象的引用。


14.2.9 析构函数和垃圾收集
Perl 维护了对大量对象引用的跟踪信息,并在引用计数达到0 时自动销毁该对象。在退出程序
时,Perl 会销毁与程序相关联的每一个对象,从而处理无用存储单元,并释放其使用过的内容。因
此用户无需关心内存清理事宜。不过,用户也可在程序中定义DESTROY 方法,以便在销毁对象
前控制该对象。


示例14.13
(The Class)
1 package Employee;
  sub new{
    my $class = shift;
    $ref={};
    bless($ref, $class);
    return $ref;
  }
2 sub DESTROY{
    my $self = shift;
3   print "Employee $self->{Name} is being destroyed.\n";
  }
1;
---------------------------------------------------------------


#!/usr/bin/perl
# User of the class
4 use Employee;
5 my $emp1 = Employee->new; # Create the object
6 { my $emp2 = Employee->new;
    $emp2->{"Name"}="Christian Dobbins";
    print "\t\t$emp2->{'Name'}\n";
  } # Create the object
7 my $emp3 = Employee->new; # Create the object
8 $emp1->{"Name"}="Dan Savage";
  $emp3->{"Name"}="Willie Rogers";
  print "Here are our remaining employees:\n";
  print "\t\t$emp1->{'Name'}\n";
  print "\t\t$emp3->{'Name'}\n";


(Output)
Christian Dobbins
Employee Christian Dobbins is being destroyed.
Here are our remaining Employees:
Dan Savage
Willie Rogers
Employee Dan Savage is being destroyed.
Employee Willie Rogers is being destroyed.


解释
1. 声明Employee 类,并定义其构造函数方法。
2. 当不再需要某个Employee 对象时,便会调用其DESTROY 方法,并打印本行内容。第6 行
   出现的对象是在一个代码块中定义的。当退出该代码块时,该对象也应予以销毁。当程序结
   束时,另一个对象也将销毁。
3. 每当对象得以销毁时,打印本行内容。
4. 这里将使用Employee 模块。
5. 通过调用构造函数方法,创建一个新的Employee 对象,得到其对象引用$emp1。
6. 在同一代码块中创建另一个Employee 对象。为该对象赋予一个“名字”,即Christian Dobbins。
   由于它是一个“my”变量,因此其作用域是受限的。也就是说,在退出该代码块时,这个对
   象也将予以销毁。这时便会调用DESTROY 方法,将它清除出内存。
7. 通过调用构造函数方法,创建第三个Employee 对象,它由$emp3 来引用。
8. 为Employee 对象赋予键/ 值对。
原创粉丝点击