一、模块简介
模块(module)就是Perl包(package)。Perl的对象基于对包中数据项的引用。
在用其它语言进行面向对象编程时,先声明一个类然后创建该类的对象(实例),特定类所有对象的行为方式是相同的,由类方法确定,可以通过定义新类或从现存类继承来创建类。
- 类是一个Perl包,其中包含提供对象方法的类;
- 方法是一个Perl子程序,类名是其第一个参数;
- 对象是对类中数据项的引用。
二、Perl中的类
一个Perl类是一个包。Perl5用双冒号(::)来标识基本类和继承类(之类)。
Perl中的继承只继承方法,必须使用自己的机制来实现数据的继承。
因为每个类是一个包,所以它有自己的名字空间及自己的符号名关联数组(详见第x章关联数组),每个类因而可以使用自己的独立符号名集。与包的引用结合,可以用单引号(‘)操作符来定位类中的变量,类中成员的定位形式如:$class‘$member。在Perl5中,可用双冒号替代单引号来获得引用,如:$class‘$member与$class::$member相同。
三、创建类
package Cocoa;
#
# Put "require" statements in for all required,imported packages
#
#
# Just add code here
#
接下来,我们往包里添加方法使之成为一个类。第一个需添加的方法是new(),它是创建对象时必须被调用的,new()方法是对象的构造函数。
四、构造函数
sub new {
my $this = {}; # Create an anonymous hash, and #self points to it.
bless $this; # Connect the hash to the package Cocoa.
return $this; # Return the reference to the hash.
}
1 #!/usr/bin/perl
2 push (@INC,‘pwd‘);
3 use Cocoa;
4 $cup = new Cocoa;
ps,
1. 一定要在构造函数中初始化变量;
2. 一定要用my函数在方法中创建变量;
3. 一定不要在方法中使用local,除非真的想吧变量传递给其他子程序;
4. 一定不要在类模块中使用全局变量。
五、方法
Perl类的方法只不过是一个Perl子程序而已,也即通常所说的成员函数。Perl的方法定义不提供任何特殊语法,但规定方法的第一个参数为对象或者其被引用的包。Perl有两种方法,静态方法以及虚方法。
静态方法的第一个参数为类名,虚方法的第一个参数为对象的引用。
六、方法的输出
如果你现在想引用Cocoa.pm包,将会得到编译错误说未找到方法,这是因为Cocoa.pm的方法还没有输出。输出方法需要Exporter模块,在包的开始部分加上下列两行:
require Exporter;
@ISA = qw (Exporter);
这两行包含上Exporter.pm模块,并把Exporter类名加入@ISA数组以供查找。接下来把你自己的类方法列在@EXPORT数组中就可以了。例如想输出方法closeMain和declareMain,语句如下:
@EXPORT = qw (declareMain , closeMain);
Perl类的继承是通过@ISA数组实现的。@ISA数组不需要在任何包中定义,然而,一旦它被定义,Perl就把它看作目录名的特殊数组。它与@INC数组类似,@INC是包含文件的寻找路径。@ISA数组含有类(包)名,当一个方法在当前包中未找到时就到@ISA中的包去寻找。@ISA中还含有当前类继承的基类名。
类中调用的所有方法必须属于同一个类或@ISA数组定义的基类。如果一个方法在@ISA数组中未找到,Perl就到AUTOLOAD()子程序中寻找,这个可选的子程序在当前包中用sub定义。若使用AUTOLOAD子程序,必须用use Autoload;语句调用autoload.pm包。AUTOLOAD子程序尝试从已安装的Perl库中装载调用的方法。如果AUTOLOAD也失败了,Perl再到UNIVERSAL类做最后一次尝试,如果仍失败,Perl就生成关于该无法解析函数的错误。
七、方法的调用
调用一个对象的方法有两种,一是通过该对象的引用(虚方法),二是直接使用类名(静态方法)。当然该方法必须已经被输出。
package Cocoa;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(setImports, declareMain, closeMain);
#
# This routine creates the references for imports in Java functions
#
sub setImports{
my $class = shift @_;
my @names = @_;
foreach (@names) {
print "import " . $_ . ";\n";
}
}
#
# This routine declares the main function in a Java script
#
sub declareMain{
my $class = shift @_;
my ( $name, $extends, $implements) = @_;
print "\n public class $name";
if ($extends) {
print " extends " . $extends;
}
if ($implements) {
print " implements " . $implements;
}
print " { \n";
}
#
# This routine declares the main function in a Java script
#
sub closeMain{
print "} \n";
}
#
# This subroutine creates the header for the file.
#
sub new {
my $this = {};
print "\n /* \n ** Created by Cocoa.pm \n ** Use at own risk \n */ \n";
bless $this;
return $this;
}1;
现在,我们写一个简单的Perl脚本来使用该类的方法,下面是创建一个Java applet源代码骨架的脚本代码:
#!/usr/bin/perl
use Cocoa;
$cup = new Cocoa;
$cup->setImports( ‘java.io.InputStream‘, ‘java.net.*‘);
$cup->declareMain( "Msg" , "java.applet.Applet", "Runnable");
$cup->closeMain();
八、重载
有时需要制定使用哪个类的方法,如两个不同的类有同名方法的时候。假设类Espresso和Qava都定义了方法grind,可以用::操作符指定使用Qava的方法:
$mess = Qava::grind("whole","lotta","bags");
Qava::grind($mess, "whole","lotta","bags");
可以根据程序的运行情况来选择使用哪个类的方法,这可以通过使用符号引用去调用来实现:
$method = $local ? "Qava::" : "Espresso::";
$cup->{$method}grind(@args);
九、析构函数
Perl跟踪对象的链接数目,当某对象的最后一个应用释放到内存池的时候,该对象就自动销毁。对象的西沟发生在代码停止后,脚本将要结束时。对于全局变量而言,析构发生在最后一行代码运行之后。
如果你想在对象被释放之前获取控制权,可以定义DESTROY()方法。DESTROY()在对象将释放前被调用,使你可以做一些清理工作。DESTROY()函数不自动调用其它DESTROY()函数,Perl不做内置的析构工作。如果构造函数从基类多次bless,DESTROY()可能需要调用其它类的DESTROY()函数。当一个对象被释放时,其内含的所有对象引用自动释放、销毁。
一般来说,不需要定义DESTROY()函数,如果需要,其形式如下:
sub DESTROY {
#
# Add code here.
#
}
因为多种目的,Perl使用了简单的、基于引用的垃圾回收系统。任何对象的引用数目必须大于0,否则该对象的内存就会被释放。当程序退出时,Perl的一个彻底的查找并销毁函数进行垃圾回收,进程中的一切被简单地删除。在UNIX类的系统中,这是多余的,但在内嵌式系统或者多线程环境中这确实是必要的。
十、继承
十一、子类方法的继承
十二、Perl类和对象的一些注释
OOP的最大好处就是代码重用。OOP用数据封装来隐藏一些复杂的代码,Perl的包和模块通过my函数提供数据封装功能,但Perl并不保证之类一定不会直接访问基类的变量,这确实减少了数据封装的好处,虽然这种动作是可以做到的,但却是一个很坏的的编程风格。
1. 一定要通过方法来访问类变量;
2. 一定不要从模块外直接访问类变量。
当编写包时,应该保证方法所需的条件已具备或者通过参数传递给它。在包内部,应保证对全局变量的访问只用通过方法传递的引用来访问。对于方法要使用的静态或者全局数据,应该在基类中使用local()定义,自雷通过调用基类来获取。有时,子类可能需要改变这种数据,这时,基类可能就不知道怎样去寻找新的数据,因此,这时最好定义对该数据的引用,子类和基类都通过引用来改变该数据。