perl 面向对象

14.1 OOP范例
14.1.1 回顾包与模块
面向对象的编程(Object-Oriented Programming)OOP 的核心是其程序组织的方式。
诸如C ++和Java 之类的面向对象编程语言都能把数据绑定到变量中,并称之为对象(object); 
将数据和方法封装到一起得到的数据结构称为一个类(class); 


若要创建一个Perl 模块,仍应使用关键字package,
其作用域是从声明包的位置直到封装块或文件的末尾。
包一般都对应于一个文件。
如果其名称中含有扩展名.pm,并且模块的第一个字母又是大写的话,Perl 就会把这个文件识别为模块.


14.1.2 一些面向对象的专用术语
表14-1 OOP关键词


14.2 类、对象和方法
本节将分析一组示例,并讨论Perl 是如何创建、管理和销毁对象的。

14.2.2 步骤

本章将详细讨论许多方面的内容。
总体而言,为了创建一个名叫对象的新数据类型,并定义它能做什么不能做什么,
需遵循如下几个步骤:
1. 确定对象内容是什么,能完成什么任务(设计)。
2. 在一种名叫类(class)的包里创建新对象(构造函数)。
3. 描述一个对象;譬如,为它设置属性(形容词)。
4. 获取对象(根据数据类型得到对象)。
5. 定义函数(动词),即对象能干什么,以及能对这个对象干什么(方法)。
6. 使用对象(定义用户接口,又称为方法)。
7. 重用对象(继承)。
8. 销毁对象(把对象清除出内容)

14.2.3 类和私有性
Perl 中的类(class)就是一种包(package)。这两个术语是可以互换的。
类一般位于.pm 模块中,并且其类名和模块名(除.pm 扩展名)相同。
如果硬要区别二者的话,则类是一种含有方法(method)的包,其中的方法是一类特殊的子例程,负责操纵对象。
一个Perl 类一般由如下几个部分组成(参见图14-1):
1. 描述对象的数据。
2. 一个名叫“bless”的函数,负责创建对象。
3. 名叫“方法”的特殊子例程,它知道如何去创建、访问、操纵和销毁对象。

由于类在本质上就是一种包,因此它也具有自己独立的符号表,
并且也可以在一个类中通过双冒号(Perl 5)或单撇号(Perl 4)访问另一个类中的数据或子例程。

图 14-1 类的组成, .pm文件



与其他语言不同,Perl 不会严格监控其模块中的公有/ 私有界限。
如要保证变量的私有性,请务必使用my 函数。
my 变量只能出现在最内层的封闭块、子例程、eval 或文件。
用户不能通过双冒号(或单撇号)从其他包中访问my 变量,
因为my 变量不和任何包相关,也不存储在创建它的包的符号表里。

示例14.1
#!/bin/perl
1 package main;
2 $name = "Susan";
3 my $birthyear = 1942;


4 package nosy;
5 print "Hello $main::name.\n";
6 print "You were born in $main::birthyear?\n";


(Output)
5 Hello Susan.
6 You were born in ?
解释
1. 包名为main。
2. 标量$name 在这个包中的作用域是全局的。
3. 标量$birthyear 对于该代码块乃至任何封闭的内层代码块而言都是局部变量。
4. 声明nosy 包。它的作用域是从声明之处直到文件末尾。
5. 通过使用包名与双冒号来限定访问全局变量$name。
6. 在这个包中,直接通过符号表是不能访问标量$birthyear 的。
   因为该标量是通过my 函数声明在另一个不同的包main 中的,不和任何符号表相关,
   而是存储在封闭代码块中创建的专用缓冲区内。


14.2.4 对象
Perl 中的对象是通过使用硬引用(指针)的方式创建的。
引用是负责保存变量地址的标量,引用就是指针。
引用也可指向其他没有名字的变量或子例程,又称匿名变量。

例如,
这里是一个指向匿名散列的引用$ref,该匿名散列中含有两个键/ 值对:
my $ref = { "Owner" => "Tom" , 
            "Price" => "25000" 
          };

若要访问匿名散列内的值,则可使用箭头运算符按地址访问$ref 引用,
如下所示:
$ref -> {"Owner"}


为了创建Perl 对象,首先应当创建引用。
一般会把该引用赋值为一个匿名散列的地址(也可将它赋值为其他任意数组或标量的地址)。
在这个散列中将含有 对象的数据成员和属性。
除了存储散列的地址之外,引用还应当获知它所属于的包。
为此用户应当先创建引用,然后将其“归入(bless)”到指定的包中。
bless 函数能把引用的“东西”(而不是引用本身)加入到包里。
它会创建一个内部指针,用于跟踪对象所属的包。
对象是归入到类(包)的实例(通常是一个散列表)。
如果没有将包作为第二个参数提供给bless 函数的话,它就会假定使用当前的包。
该函数将返回被归入的对象的引用(有关bless 函数的完整讨论内容请参阅“bless 函数”一节)。

my $ref = { Owner => "Tom", 
            Price => 250000 
          };                  # This is the object
bless( $ref, Class);          # The object is blessed into the package named Class
return $ref;                  # A reference to the object is returned


当把对象归入到类中之后,用户就不必使用@EXPORT_OK 或@EXPORT 数组去导出对象了。
事实上,作为一种通用的规则,如果模块是面向对象的话,则什么也不用导出。


House 类。
图14-2 演示了应当如何虚拟化一个House 类。


图 14-2


1. 首先必须创建一个包。这个包的名字是House,位于文件House.pm 中。
   在OO 环境中,包亦可称作是类。
   请注意,类名必须与不带.pm 扩展名的文件名相同。
   为了表明其封装性,这里用一个house 对象囊括所有描述数据。
   属性又称为特性,负责描述对象的特点,譬如对象的属主、样式、尺寸、颜色等等。
   在这里的示例中,
     house 的属性包括Owner、Style 和Price。
   在Perl 中,常常通过一个匿名散列表来描述对象,散列表中的键/ 值对代表着对象的属性。
2. 在house 对象外面,列出了一系列子例程。
   在面向对象的世界里,这些子例程又称作公有方法(public method),
   它们为用户提供了访问对象的途径。
   它们可以用于访问某个对象,但是这么做的前提是这个对象必需是已经存在的。
   它们往往负责描述对象的“行为”;
   譬如:该对象能干什么,以及能对该对象做什么。
   事实上,方法应当是访问对象的惟一途径。
   对于house 对象而言,访问它的一个方法可以是移入操作;另一个方法负责清理它; 
   第三个则负责展示它;等等。在Perl 中,方法只是一种特别的子例程。


与其他OO 编程语言不同,Perl 并没有提供特殊关键字public、private 以及protected。
Perl 的包机制负责创建类,并在类里面保存数据和子例程(即方法)。
my 函数使得变量的作用域仅限于本包;
bless 函数则保证了对象在创建时知道自己属于哪个类。
总而言之,对象通常都是一个指向匿名散列表、数组或标量的指针,可通过称作方法的特殊函数予以处理。
方法可通过指针来访问指定的对象。


14.2.5 bless 函数
读者可以把bless 函数看作是负责创建一种名叫“对象”的新数据类型的函数。
在图14-2 中,为了创建House 对象,
首先须借助一个匿名散列赋予它属性值,然后再返回对象的指针,即一个地址。
在自己的程序内,读者可把上述地址看作是house 对象实际所在的内存地址。
bless 函数可以直接找到这个地址,并创建一个House 对象。
如果读者归入(bless)一个标量或指针的话,便会把它们转换为一个对象。
用专业术语来讲,bless 函数的第一个参数必需是一个指针。
借助指向所在包的引用,bless 函数在内部为指针指向的任何内容(即引用目标,referent)都加上了标签。
这就是对象的创建流程。
如果在第二个函数中没有列出包(类)的话,bless 函数便会把对象标记为属于当前的包。
bless 函数使用引用寻找所需的对象,并能返回指向该对象的引用。
由于bless 操作将把对象和特定的包(类)关联起来,因此Perl 总是能知道各个对象各自属于什么包。
用户可以把对象归入到一个类中,然后再重新归入到另一个类,依次类推。
但是在同一时刻,一个对象只能属于一个类。


格式:
bless REFERENCE, CLASSNAME
bless REFERENCE


示例14.2
my $reference = {};
return bless( $reference, $class);


示例14.3 说明了如何创建一个对象。
首先应创建一个匿名散列,
然后把它归入到一个包中,
并通过ref 函数检查该对象是否真的位于这个包中。


示例14.3
(The Script)
1 package House;                    # Package declaration
2 my $ref = { "Owner"=>"Tom",       # Anonymous hash; data for the package
                        "Price"=>"25000",     # Properties/attributes
            };
3 bless($ref, House);
  # The bless function creates the object. The hash referenced by
  # $ref is the object. It is blessed into
  # the package; i.e., an internal pointer is created to keep track
  # of the package where it belongs.
4 print "The bless function tags the hash with its package name.\n";
5 print "The value of \$ref is: $ref.\n";
6 print "The ref function returns the class (package) name:",ref($ref), ".\n";


(Output)
4 The bless function tags the hash with its package name.
5 The value of $ref is: House=HASH(0x5a08a8).
6 The ref function returns the class (package) name: House.


解释
1. 声明House 包。这是一个类。
2. 将引用$ref 赋值为匿名散列的地址,该散列含有两个键/ 值对。这些散列值代表了所述对象的属性值。
3. bless 函数带有一个或两个参数。
   其中第一个参数是一个引用,第二个参数则是包的名称。
   如果没有提供第二个参数的话,就假定使用的是当前包。
   在本示例中,当前包名为House。现在用户可以看到,这个引用指向的对象位于House 包中。
5. 打印引用的值(即对象的地址)。它实际上是指向House 包内一个散列表的引用;简单地讲,
   它就是指向新的House 对象的一个指针。
6. 如果ref 函数的参数是一个指针,并指向某个对象的话,该函数便会返回这个对象所归属到的包的名称。


14.2.6 方法
定义:
方法是用于操作对象的子例程。
它是一种属于类的特殊子例程,并要求其第一个参数必需是包名或指向对象的引用。
这个参数会由Perl 隐式地赋值。如果没有这个参数,它就和其他的子例程没有分别。
方法主要用于创建对象、赋值或更改对象中的数据、或者从对象中检索数据。


方法的类型。 
方法有两种不同的类型:类(静态)方法和实例(虚)方法。
类方法取类名为其第一个参数,而实例方法则把对象引用作为第一个参数。


类方法(class method),
是一种影响整个类的子例程;
例如,它能创建一个对象,或者作用在一系列对象上。
类方法需要把类名作为其第一个参数。


在面向对象的编程中,构造函数(constructor)就是一种负责创建对象的类方法。
在Perl 中,一般会把这个方法称为new,当然读者也可任意给它命名。
对象的创建过程又称作是对象(或实例)的实例化(instantiation)。
面向对象的程序一般都通过实例方法(也称作访问方法)来控制对象数据的赋值、修改和检索行为方式。
只有在对象创建完毕后,才能使用其实例方法。
负责创建对象的方法又称作是构造函数。它会返回指向对象的引用。
一旦得到了指向新对象的引用(一般命名为$this 或$self),实例方法便可通过该引用访问这个对象。


实例方法
的第一个参数应当是指向对象的引用。然后它会通过该引用来操作指定的对象。


调用方法。
Perl 提供可调用方法的特殊语法。这里不使用package::function 形式的语法,
而是以另外两种方法来调用方法:
类方法调用和实例方法调用。
每种方法调用都有两种语法:
  面向对象语法(object-oriented syntax)和
  间接语法(indirect syntax)。


如果用户要使用对象,则其中任意一种语法都是可以接受的。
这里不推荐读者使用老式的带双冒号的方法调用方式。


请读者牢记:
和普通的子例程不同,方法总会隐含传递一个参数,该参数可以是类名,也可能是指向对象的引用。
譬如,如果用户以三个参数调用某个方法,则实际传递过去的参数会有四个。
在面向对象的样式下,其第一个参数值可在箭头的左边看到。


类方法调用
假定方法名为new,其返回值$ref 是指向对象的指针。
1) $ref = class->new( list of arguments );   # object-oriented syntax
2) $ref = new class ( list of arguments );   # indirect syntax


如果类名为House,则Perl 会把
$ref = House -> new();
转换成:
$ref = House :: new(House) ;


实例方法调用
假定方法名为display,指向对象的引用名为$ref。
1) $ref->display( list of arguments ); # object-oriented syntax
2) display $ref (list of arguments );  # indirect syntax
上述第一个示例使用的方法称作面向对象的方法;
而第二个实例用到了箭头运算符,又称作是间接语法。
当Perl 看到要调用的以上方法时,就知道了这些方法所在的类,
因为上述对象都已执行了归入操作(由一个内部指针指明其所在位置)。


如果读者调用
display $ref ( arguments ...) ;
或者
$ref -> display(arguments ...) ;


其中$ref 指向House 类中的一个对象,那么Perl 就会把上述内容翻译为:
House :: display ( $ref , arguments ... ) ;


14.2.7 面向对象的模块样式
图14-3 展示了典型的面向对象模块的布局。创建模块的文件是一个.pm 文件。
在这个示例中,.pm 文件全名为House.pm。
该文件由一个包声明组成。包又可称作是类,因此这就是一个House类。
该类中包含多个子例程,即方法。
其中第一种方法的名字叫new,是构造函数方法。该方法负责定义和创建(构造)指定的对象。
当模块使用者调用new 方法时,就能得到一个指向新建的House对象的引用(即house 对象地址)。
new 方法以所在的类名作为其第一个参数。
这个方法不仅可以创建对象,还负责归入对象,从而让对象始终知道自己所属的类(包)。


后两种方法则称作访问方法或实例方法。
它们负责保存或读取对象中的数据。在创建好对象实例之前是不能使用这些方法的。
(除非房子已经建好,否则你是无法走进或展示它的。)
当用户获得了指向对象的引用后,便可使用该引用去调用实例方法。


继续看图14-3,我们可以看到,
对象的数据都是在一个匿名散列中予以描述的(这里亦可使用其他任何数据类型),
而对象的地址则赋值给一个私有(my)的引用。(然后通过实例方法set_data对对象数据进行赋值。)
bless 函数使用对象所属的类名来标记对象,并返回指向对象的指针;
即,当模块使用者调用构造函数时,得到的便是指向新对象的引用。
构造函数能够“实例化”对象(譬如创建一个house 对象)。
用户可以创建任意多个对象,Perl 会为每个对象提供单独的地址。
对象创建完毕后,便可使用实例方法(常称作setter 和getter 方法)来操纵这个对象。
用户可以调用实例方法去保存或检索对象数据。
在调用实例方法时,用户必须提供一个指向该对象的引用,以免访问错误的对象。
实例方法通常都把一个指向对象的引用当成其第一个参数。模块的设计方法有很多种。
下图14-3 只是其中一种简单的途径。


图 14-3




类构造函数方法。
构造函数是一个OOP(面向对象编程)术语,它指的是一种类方法,负责在类中创建并初始化相应的类对象。
构造函数没有特殊的语法。它只是一种让用户获得归属包引用的方法。
Perl 类的第一个方法(即包中第一个子例程)一般都会创建(指向对象的)引用,并将该引用归入到包内。
该方法的名字一般都是new,因为它将建立一个新的“东西”。不过读者亦可将它命名为其他名字,
譬如create、construct、initiate 等等。
一般而言,由new 子例程生成的对象都是匿名的散列表或匿名数组。
用户可以向该匿名散列或数组赋予描述对象性质的数据。
这些数据常被称作是对象的属性(property)或特性(attribute)。
总体而言,属性定义了对象的状态。


示例14.4
(The Module: House.pm)
1 package House;                 # Class
2 sub new {                      # Class method called a constructor
3   my $class = shift;
4   my $ref={"Owner"=>undef,     # Attributes of the object
             "Price" =>undef,    # Values will be assigned later
            };
5   bless($ref, $class);
                                 # $ref now references an object in this class
6   return $ref;                 # A reference to the object is returned
}
1;
----------------------------------------


(The User of the Module)
#!/usr/bin/perl
7 use House;
8 my $houseref = House->new();
  # call the new method and create the object
9 # my $houseref = new House; another way to call the new method
10 print "\$houseref in main belongs to class ", ref($houseref),".\n";


(Output)
10 $houseref in main belongs to class House.


解释
1. 声明House 包。亦可称之为类,因为它含有一个处理对象引用的方法。
2. 子例程new 在OOP 术语中又称为是构造函数。构造函数的主要功能是创建并初始化对象。
   在Perl 中,构造函数并不包含任何特殊的语法。
   构造函数是一个类方法,因为它的第一个参数是类的名字。
   该子例程能把一个经过引用的“东西”(对象)归属到类中,并返回一个指向该对象的引用。
   该子例程又称作方法,而它处理的“东西”又叫对象。它们所在的包又称作类。
3. 此类子例程接受的第一个参数是包名或类名,在本例中就是House。
   这是方法和子例程之间的又一个不同之处。方法的第一个参数必需是一个类名或对象名。
4. 把引用$ref 赋值为一个匿名散列(对象)的地址。这里把它的键赋值为undef,意味着目前
   它的值尚未定义,将在以后予以定义。
5. 将$ref 指向的“东西”归入类$class,并转换为相应的对象。
6. 把一个指向对象的指针返回给构造函数的调用者。
7. 这是另一个使用House 模块的脚本。在shbang 行之后,通过use 语句把模块House.pm 载入内存。
8. 以包/ 类名House 作为第一个参数,调用构造函数new。该函数将返回一个$houseref 引用,
   指向一个匿名散列,即新创建的对象。Perl 会隐式地把类名作为第一个参数发送给new() 方法。
9. Perl 会把$houseref = $House->new() 翻译为$houseref = House::new(House) ;
   这一行内容已经注释掉了。它展示了另一种调用方法的途径,即间接语法(indirect syntax)。
   读者可选用其中任意一种途径。
10. 如果引用已经归入到指定类中,ref 函数就能返回这个类(包)的名字。


类方法和实例方法。
回顾:
类方法,又称作静态方法,是无需对象实例就能工作的方法或子例程。
它们是独立的的函数,代表了类的行为。
属于类方法的例子包括负责计算支票数额的函数,以及从数据库中获得姓名列表的函数。
最为常见的类方法莫过于构造函数方法。它是负责创建对象的方法。
构造函数的第一个参数是类(包)的名字,并能作用在整个类上。
面向对象的程序常常使用访问方法或实例方法来控制对象数据的修改、检索和显示方式。
为了处理正确的对象,
实例方法(访问方法)需要提供一个对象实例;即对一个已有对象的引用。
如果需要在一个方法中以不同方式表达数据的话,只要提供给用户的接口保持不变,则其他方法就不必受其影响。
示例14.5 中的实例方法用作访问函数,负责显示类里面的数据成员。
实例方法取一个对象引用为其第一个参数。在调用实例方法时,请读者注意观察位于-> 左侧的值。
这个值是一个对象,它会隐式地作为第一个参数传递给正在调用的方法。
(如果位于-> 左侧的值是类名的话,则会把类名作为第一个参数传递给方法;
譬如,构造函数就是以类名作为其第一个参数的。)


示例14.5
#!/usr/bin/perl
1 package House;
2 sub new{             # Class/Static method
3   my $class = shift;
4   my $ref={};        # Anonymous and empty hash
5   bless($ref);
6   return $ref;
  }
7 sub set_owner{       # Instance/Virtual method
8   my $self = shift;
9   print "\$self is a class ", ref($self)," reference.\n";
10  $self->{"Owner"} = shift;
  }
11 sub display_owner {
12   my $self = shift;      # The object reference is the first argument
13   print $self->{"Owner"},"\n";
}
1;


#!/usr/bin/perl
# The user of the class
14 use House;
15 my $house = House->new;              # Call class method
16 $house->set_owner ("Tom Savage");    # Call instance method
17 $house->display_owner;               # Call instance method


(Output)
9  $self is a class House reference.
13 Tom Savage


解释
1. 声明House 包。亦可称之为House 类。
2. 类方法new 是构造函数。它负责创建一个引用,并将其归入House 类中。
3. 把变量$class 赋值为类的名字,后者也是构造函数new 的第一个参数。
4. 把引用$ref 赋值为一个空的匿名散列。
5. 将$ref 指向的“东西”(即匿名散列)归入House 类中。这样一来,该“东西”就变成了一个对象。
6. 从new 方法把指向对象的引用返回给调用者。
7. 定义实例方法set_name。
8. 将指向对象的引用从@_ 数组转移到$self。
   现在便可使用实例方法操纵对象了,因为已经获得了对该对象的引用。
9. ref 返回类House 的名称。只有将对象成功归入后,$ref 才能返回其类名。
10. 从@_ 数组转移第二个参数。把Tom Savage 作为一个值赋予Owner 键。
    然后使用该实例方法(又叫setter 方法)将数据赋值给对象。
11. 调用实例方法display_owner,访问对象内的数据;在本例中,即打印匿名散列中的Owner字段值。
12. 该方法的第一个参数是一个指向对象的引用。从@_ 数组将对象引用转移到$self 中。
13. 显示键Owner 的字段值。
14. 这段程序使用了House.pm 模块。
15. 调用new 构造函数方法,返回指向一个House 对象的引用;
    即指向House 类中一个匿名散列表的引用。
    构造函数方法new 通常至少传递一个参数,也就是类的名字。
    House->new()将翻译为House::new(House)。
16. 以Tom Savage 为参数调用实例函数set_owner。
    请记住,传递给实例方法的第一个参数必需是指向对象的引用,
    而Tom Savage 则是第二个参数:
    $house->set_owner ("Tom Savage" ) 会转译为 House::set_owner( $house , "Tom Savage");
17. 调用display 方法。该方法负责显示匿名散列的值,即Tom Savage。
    传递参数到构造函数方法。实例变量常用于在创建对象时对它进行初始化。
    通过这种方式,便可在每次创建对象时定制其内容。
    用户可以把描述对象的数据作为参数传递给构造函数方法。
    之所以把它们称作实例变量(instance variable),
    是因为程序只有在创建对象或初始化对象时才会生成它们。
    一般都通过匿名散列或匿名数组来保存实例变量。在下面的示例14.6 中,
    该对象“拥有(has a)”或“含有(contains a)”两个实例变量owner 和price。


示例14.6
(The Module: House.pm)
1 package House;
2 sub new{                      # Constructor method
3   my $class = shift;
4   my ($owner, $salary) = @_;  # Instance variables
5   my $ref={"Owner"=>$owner,   # Instance variables to
             "Price"=>$price,   # initialize the object
            };
6   bless($ref, $class);
7   return $ref;
  }
8 sub display_object {          # An instance method
9   my $self = shift;           # The name of the object is passed
10  while( ($key, $value)=each %$self){
      print "$key: $value \n";
    }
  }
1;
-------------------------------------------------------------------


(The Script)
#!/usr/bin/perl
# User of the class; another program
11 use House;
# Documentation explaining how to use the House
# package is called the public interface.
# It tells the programmer how to use the class.
# To create a House object requires two arguments,
# an owner and a price.
# See "Public User Interface—Documenting Classes" on page 474
# to create documentation.
# my $house1 = new House("Tom Savage", 250000);
# Invoking constructor--two ways.
12 my $house1 = House->new("Tom Savage", 250000);
13 my $house2 = House->new("Devin Quigley", 55000);


# Two objects have been created.
14 $house1->display_object;
15 $house2->display_object;
16 print "$house1, $house2\n";


(Output)
14 Owner: Tom Savage
Price: 250000
15 Owner: Devin Quigley
Price: 55000
16 House=HASH(0x9d450), House=HASH(0xa454c)


解释
1. 声明House 包。
2. 将类方法new 定义为构造函数。
3. 类方法的第一个参数是类(包)的名字。
4. 从剩余的参数列表中创建实例变量。
5. 将匿名数组的地址赋值给引用$ref。这里的键都是硬编码的,而值则由实例参数提供。
6. 将引用$ref 指向的“东西”归入类中,从而得到一个新的对象。
7. 在调用方法时返回引用$ref 的值。
8. 子程序display_object 是一个实例方法。在该类中予以定义。
9. 实例方法的第一个参数是指向对象的引用。
10. while 循环负责通过each 函数从$self 指向的散列(对象)中获得键和值。
11. 类的用户把House.pm 加载到命名空间。
12. 以三个参数调用new 方法:House、Tom Savage 和250000。其中第一个参数是类的名称。
    读者看不到这个参数,因为它是由Perl 隐式发送给构造函数的。这里惟一的要求是把Owner
    值放在第一个参数,而将Price 值作为第二个参数。这里没有提供任何错误检查机制。该示
    例只是为了说明如何将参数传递给构造函数。返回到$house1 的值是一个指向散列对象的引用。
13. 以不同的参数再次调用new 方法,新的参数是Devin Quigley 和550000。返回给引用$house2
    的值是指向另一个对象的引用。这里使用new 方法创建了两个对象。用户亦可创建任意多
    个对象。这些对象将各自拥有独立的地址,正如第16 行的输出内容所示。由于在构造函数
    中都完成了对象的归入处理,Perl 知道这些对象都位于House 类中。
14. 调用实例方法,显示$house1 所引用对象的数据。
15. 再次调用实例方法,显示$house2 所引用对象的数据。
16. 打印这两个对象的地址。
    传递参数到实例方法。 实例方法的第一个参数必需是指向对象的引用。在被调用的方法中,这
    个值一般都是从@_ 数组转移而来的,并保存在my 变量$self 或$this 中,事实上变量的名称在这
    里根本无关紧要。其余参数的处理流程与常规子例程相同。


示例14.7
#!/bin/perl
# Program to demonstrate passing arguments to an instance method.
# When method is called, user can select what he wants returned.
1 package House;
2 sub new{             # Constructor, class method
    my $class = shift;
    my ($owner, $salary, $style) = @_;
    my $ref={ "Owner"=>$name,
              "Price"=>$salary,
              "Style"=>$style,
            };
    return bless($ref, $class);
  }
3 sub display {         # Instance method
4   my $self = shift; # Object reference is the first argument
5   foreach $key ( @_){
6     print "$key: $self->{$key}\n";
    }
  }
1;




(The Script)
#!/bin/perl
# User of the class--Another program
7 use House;
8 my $house = House->new("Tom Savage", 250000, "Cape Cod");
9 $house->display ("Owner", "Style");
# Passing arguments to instance method


(Output)
Owner: Tom Savage
Style: Cape Cod
解释
1. 声明House 包。由于这个包含有归入的引用与方法,因此也可称之为一个类。
2. 本行的new 方法是一个构造函数。该对象是一个匿名散列,含有三个键/ 值对。这些键值对
   又称为对象的属性。将它们的值作为参数传递给构造函数,并创建一个对象。该对象由$ref
   引用,归入到House 类中。因此,Perl 能够追踪这些对象属于哪个包。
3. 定义实例方法display。
4. 实例方法display 的第一个参数是指向House 对象的引用。将该参数移出来,并赋值给$self。
5. foreach 循环负责逐一遍历@_ 数组中的剩余元素,并依次将每个参数赋值给变量$key。变
   量$key 是一个匿名数组中的关键字。
6. 打印从关联数组中选定的值。
7. 类的调用者将该模块载入到程序中。
8. 调用构造函数方法,创建一个新的House 对象。
9. 以给定参数调用实例方法。其传递的第一个参数是一个指向对象的指针,即$house,不过用
   户是看不到这个参数的。其余参数则由用户自行提供,它们都位于括号内。


命名参数。
到现在为止,上面这些实例都使用的House 对象。
在下面的示例中,我们将创建一个新的Employee 对象。
Employee 构造函数将接受一些用于描述雇员属性情况的参数。
如果构造函数方法需要按顺序传递姓名、地址和薪水的话,
程序将很容易以错误的顺序传递这些参数,从而导致把地址值赋予姓名,或者将姓名值赋予薪水,等等。
如果使用了命名变量,就能提供一种方法,
可以确保不论以何种顺序把参数传递给方法,都能将参数值赋予正确的属性。
用户可通过调用或驱动程序以键/ 值对的形式传递参数,然后由构造函数将这些参数接受为一个散列表。
示例13.8 展示了如何把命名参数传递给方法,以及如何在方法中传递命名参数。


用户/ 驱动程序
示例14.8
#!/usr/bin/perl
# User of Employee.pm--See Example 14.9 for module
1 use Employee;
2 use warnings;
  use strict;
3 my($name, $extension, $address, $basepay, $employee);
4 print "Enter the employee's name. ";
  chomp($name=);
  print "Enter the employee's phone extension. ";
  chomp($address=);
  print "Enter the employee's address. ";
  chomp($address=);
  print "Enter the employee's basepay. ";
  chomp($basepay=);
  # Passing parameters as a hash
5 $employee = Employee->new( "Name"=>$name,
                             "Address"=>$address,
                             "Extension"=>$extension,
                             "PayCheck"=>$basepay,
                           );
  print "\nThe statistics for $name are: \n";
6 $employee->get_stats;


(Output)
Enter the employee's name. Daniel Savage
Enter the employee's phone extension. 2534
Enter the employee's address. 999 Mission Ave, Somewhere, CA
Enter the employee's basepay. 2200
The statistics for Daniel Savage are:
Address = 999 Mission Ave, Somewhere, CA
PayCheck = 2200
IdNum = Employee Id not provided!
Extension = 2534
Name = Daniel Savage


解释
1. 该程序用到了Employee.pm 模块。
2. 对于可能发生的错误,触发警告信息、strict 编译指示符将跟踪全局和未定义的变量、裸词等。
3. 创建一个词法上私有的变量列表。
4. 要求程序的用户输入想要传递给Employee 模块的信息。
5. 调用构造函数,并以键/ 值对的形式传递参数;即,在Employee 中将散列传递给构造函数。
( 详见示例14.9。)然后返回转向对象的引用,并将它赋值给$employee。
6. 调用实例方法get_stats,显示雇员的属性


面向对象的模块
示例14.9
# Module Employee.pm--See Example 14.8 to use this module.
1 package Employee;
2 use Carp;
3 sub new {
4   my $class = shift;
5   my(%params)=@_; # Receiving the hash that was passed
6   my $objptr={
7        "Name"=>$params{"Name"} || croak("No name assigned"),
         "Extension"=>$params{"Extension"},
8        "Address"=>$params{"Address"},
         "PayCheck"=>$params{"PayCheck"} ||
         croak("No pay assigned"),
9        ((defined $params{"IdNum"})?("IdNum"=>$params{"IdNum"}):
         ("IdNum"=>"Employee's id was not provided!"
         )),
     };
10   return bless($objptr,$class);
   }
11 sub get_stats{
12   my $self=shift;
13   while( ($key, $value)=each %$self){
       print $key, " = ", $value, "\n";
     }
     print "\n";
   }
1;


解释
1. 声明Employee 类(包)。
2. 使用标准Perl 库中的Carp 模块处理错误信息。这里没有使用Perl 内建的die 函数,而是可
   以使用来自Carp 模块的croak 方法,以便在碰到出错情况时退出程序,并打印有关出错原
   因的更详细的信息。
3. 定义构造函数方法new。
4. 构造函数方法的第一个参数是类名。将其移出@_ 数组,并赋值给$class。
5. 将@_ 数组中的其余参数赋值给散列变量%params。然后以键/ 值对的形式将该变量传递给
   构造函数。
6. 将引用$ref 赋值为一个匿名散列的地址。
7. 为键Name 赋值,并从散列%params 中检索该值。这里已经完成了出错检查。如果没有为键
   Name 提供相应值的话,程序就会执行croak 函数,以便让用户知道程序并没有给Name 赋
   值,然后退出程序。
8. 从散列%params 中获取参数值,并赋值给Address 属性。
9. 这个例子说明了如何确保模块的用户传递了正确的参数。条件语句读作:如果散列%params
   中存在定义好的键IdNum,则获取其值并赋予IdNum;否则,在程序运行时告诉用户他忘了
   指定该参数。在使用croak 函数的示例中,如果用户没有按照要求提供输入的话,程序便会
  退出执行。而在这种形式的检查中,程序将继续执行。
10. 为属性赋值,然后将指向对象的引用归入到类,并把该引用返回给调用者。
11. 定义实例方法get_stats。
12. 从@_ 数组中移出第一个参数,并将它赋值给$self。该参数是指向对象的指针。
13. 进入while 循环。each 函数负责返回并打印对象中的每一个键/ 值对。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值