老式面向对象Perl
如果您需要编写无依赖的面向对象Perl代码,则必须使用老式Perl语法。本文描述了老式面向对象Perl的主要功能,包括类声明、构造函数、析构函数、方法、属性、访问器和继承。
如果您想使用现代Perl工具编写面向对象Perl代码,请考虑使用Moose进行功能丰富的实现,包括类型检查、角色和访问器方法。另一个选择是Moo,它提供了一个快速的、简约的实现,没有Moose的糖(或开销)。
类
Perl类是在带有包声明的Perl模块文件 (*.pm) 中定义的。至少,类必须包含一个构造函数方法,并可选择包含其他类方法和属性。与所有Perl包一样,它必须返回一个真值(通常是1,放在文件末尾)。一个最小示例Perl类如下所示
# This is the package declaration
package Shape;
# This is the constructor method
sub new {
return bless {}, shift;
}
1;
示例Shape类必须保存为名为‘Shape.pm’的文件。
构造函数方法
构造函数方法是一个Perl子程序,它返回一个类的实例的对象。按照惯例,构造函数方法的名称为‘new’,但它可以是任何有效的子程序名称。构造函数方法通过在哈希引用上使用bless函数和类名(包名)来实现。当调用new时,包名‘Shape’位于默认数组@_中,使用shift将包名从@_中取出并传递给bless函数。让我们修改构造函数方法以更清楚地传达这种行为
package Shape;
sub new {
my $class = shift;
my $self = {};
return bless $self, $class;
}
1;
这种编写构造函数的风格是Perl代码中经常看到的一种常见模式。它不仅更清楚地传达了代码的行为,而且还允许轻松扩展到$self,例如添加默认属性和/或接受初始化参数。
属性
基本上,Perl对象是对哈希引用的bless引用。这个哈希引用存储了对象的属性数据,即键值对。让我们给我们的Shape类添加一些有用的属性
package Shape;
sub new {
my $class = shift;
my $self = {
color => 'black',
length => 1,
width => 1,
};
return bless $self, $class;
}
1;
Shape类已被赋予颜色、长度和宽度属性。我们可以通过使用我们的Shape类在Perl程序中访问这些属性,让我们称这个程序为‘Draw.pl’(它应该保存与Shape.pm相同的目录中)
use strict;
use warnings;
use feature qw/say/;
use Shape;
# create a new Shape object
my $shape = Shape->new;
# print the shape object attributes
say $shape->{color};
say $shape->{length};
say $shape->{width};
让我们回顾一下关键行:‘use feature qw/say/;’启用say函数,它打印并追加换行符。‘use Shape;’导入Shape类,my $shape = Shape->new;创建一个新的Shape对象。使用$shape,我们可以使用花括号括起来的属性名称的解引用箭头->访问属性,并使用say函数打印属性。
使用构造函数参数设置属性值
目前,Shape.pm相当僵化;我们使用的每个Shape对象都将具有相同颜色、长度和宽度的值。我们可以通过接受设置颜色、长度和宽度属性值的参数来增加Shape类的灵活性。让我们相应地修改Shape.pm的构造函数
package Shape;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color},
length => $args->{length},
width => $args->{width},
};
return bless $self, $class;
}
1;
在Perl中调用任何子程序时,参数都包含在默认数组变量@_中。Shape.pm构造函数现在期望一个包含属性值的哈希引用作为其参数,并将其分配给$args。我们可以更新Draw.pl以传递这些参数
use strict;
use warnings;
use feature qw/say/;
use Shape;
# pass color, length and width arguments to the constructor
my $shape = Shape->new({
color => 'red',
length=> 2,
width => 2,
});
# print the shape object attributes
say $shape->{color};
say $shape->{length};
say $shape->{width};
创建默认属性值
现在我们可以创建不同颜色、长度和宽度的Shape对象。但如果在我们的Perl程序中没有传递所有参数呢?在这种情况下,对象将使用空值初始化这些属性。为了避免这种情况,我们可以在对象构造时设置默认值,如果存在参数则会覆盖这些默认值。我们可以在Shape.pm中使用逻辑或运算符||来达到这个效果。
package Shape;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
length => $args->{length} || 1,
width => $args->{width} || 1,
};
return bless $self, $class;
}
1;
现在我们拥有了最好的两者:当使用Shape.pm时,您可以选择性地传递属性值,或者Shape将使用默认值构造。我们可以通过更新和运行Draw.pl来证明这一点。
use strict;
use warnings;
use feature qw/say/;
use Shape;
# pass color, length and width arguments to the constructor
my $red_shape = Shape->new({
color => 'red',
});
# use the default attribute values
my $black_shape = Shape->new;
say $red_shape->{color};
say $black_shape->{color};
在Draw.pl中,我们使用一个参数对(color => red)初始化了$red_shape,但对于$black_shape,我们没有提供任何参数,所以颜色属性默认为黑色。
动态添加属性
可能向对象的哈希引用中插入新属性,例如,我们可以使用长度和宽度属性计算形状的面积。
use strict;
use warnings;
use feature qw/say/;
use Shape;
# pass color, length and width arguments to the constructor
my $red_shape = Shape->new({
color => 'red',
length=> 2,
width => 2,
});
# calculate the area of $red_shape
my $area = $red_shape->{length} * $red_shape->{width};
# insert the area attribute and value into $red_shape
$red_shape->{area} = $area;
say $red_shape->{area};
现在$red_shape具有属性:颜色、长度、宽度和面积。
方法
方法只是属于类的一个Perl子程序。Shape类已经有一个方法,即构造函数new。让我们向Shape.pm添加一个新的方法来计算和返回面积。
package Shape;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
length => $args->{length} || 1,
width => $args->{width} || 1,
};
return bless $self, $class;
}
sub get_area {
my $self = shift;
my $area = $self->{length} * $self->{width};
return $area;
}
1;
我们添加了get_area方法(在命名方法时使用动词-名词风格是很好的实践)。当一个对象方法被调用时,默认数组@_的第一个元素将包含包名和对象的引用。在get_area方法中,我们将这个参数存储在$self中。然后,我们将长度和宽度属性解引用以计算并返回$area。我们可以更新Draw.pl以使用新的面积方法。
use strict;
use warnings;
use feature qw/say/;
use Shape;
# pass color, length and width arguments to the constructor
my $red_shape = Shape->new({
color => 'red',
length=> 2,
width => 2,
});
# call the area method and print the value
say $red_shape->get_area;
访问器方法
访问器方法是子程序,用于访问对象属性。与直接解引用对象哈希引用中的属性相比,这可以使代码更易于阅读和维护(尤其是如果您遵循动词-名词风格的方法命名)。我们可以通过更新Shape.pm来为其颜色属性添加get和set方法。
package Shape;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
length => $args->{length} || 1,
width => $args->{width} || 1,
};
return bless $self, $class;
}
sub get_area {
my $self = shift;
return $self->{length} * $self->{width};
}
sub get_color {
my $self = shift;
return $self->{color};
}
sub set_color {
my ($self, $color) = @_;
$self->{color} = $color;
}
1;
让我们更新Draw.pl以使用新方法。
use strict;
use warnings;
use feature qw/say/;
use Shape;
# pass color argument to the constructor
my $shape = Shape->new({
color => 'red',
});
# print the shape color using get_color method
say $shape->get_color;
# set the shape color to blue
$shape->set_color('blue');
# print the shape color using get_color method
say $shape->get_color;
使用示例方法get_color和set_color,应该很明显如何编写额外的get和set方法来处理长度和宽度属性。
构造方法
构造方法是一个内部子程序,用于在构造时设置对象属性的值。Perl程序员使用的约定是使用名称前缀下划线来表示内部方法。让我们向Shape.pm添加一个构造方法,使用Time::Piece核心模块设置创建日期和时间。
package Shape;
use Time::Piece;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
length => $args->{length} || 1,
width => $args->{width} || 1,
};
my $object = bless $self, $class;
$object->_set_datetime;
return $object;
}
sub get_area {
my $self = shift;
return $self->{length} * $self->{width};
}
sub get_color {
my $self = shift;
return $self->{color};
}
sub set_color {
my ($self, $color) = @_;
$self->{color} = $color;
}
sub _set_datetime {
my $self = shift;
my $t = localtime;
$self->{datetime} = $t->datetime;
}
sub get_datetime {
my $self = shift;
return $self->{datetime};
}
1;
_set_datetime构造方法在new方法中被调用。还添加了一个相应的访问器来获取日期和时间。我们可以通过更新Draw.pl来测试它。
use strict;
use warnings;
use feature qw/say/;
use Shape;
my $shape = Shape->new;
# get datetime
say $shape->get_datetime;
析构方法
析构方法在Perl中当对象的引用全部超出作用域时会自动调用 - 它永远不会直接调用。如果类创建需要清理的临时文件或线程,析构方法很有用。它们还可以用于事件日志记录。Perl提供了一个特殊的析构方法名称,'DESTROY',必须在声明析构方法时使用。
package Shape;
use Time::Piece;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
length => $args->{length} || 1,
width => $args->{width} || 1,
};
my $object = bless $self, $class;
$object->_set_datetime;
return $object;
}
# The new destructor method, called automatically by Perl
sub DESTROY {
my $self = shift;
print "I have been garbage-collected!\n";
}
sub get_area {
my $self = shift;
return $self->{length} * $self->{width};
}
sub get_color {
my $self = shift;
return $self->{color};
}
sub set_color {
my ($self, $color) = @_;
$self->{color} = $color;
}
sub _set_datetime {
my $self = shift;
my $t = localtime;
$self->{datetime} = $t->datetime;
}
sub get_datetime {
my $self = shift;
return $self->{datetime};
}
1;
继承
面向对象编程的一个关键概念是能够创建现有类的子类。这些子类将继承其父类的方法和属性,这允许程序员通过将可重用的通用代码与专业代码分离来保持DRY。
假设我们想实现圆形形状。目前Shape.pm具有长度和宽度属性,这些属性适用于矩形形状,但不适用于圆形,因为圆形需要半径、直径和周长属性。我们可以从Shape.pm派生出一个新的Circle类,该类继承Shape的方法并实现新的特定于圆形的功能。
package Shape::Circle;
use parent Shape;
sub new {
my ($class, $args) = @_;
my $self = {
color => $args->{color} || 'black',
diameter => $args->{diameter} || 1,
};
my $object = bless $self, $class;
$object->_set_datetime;
return $object;
}
sub get_radius {
my $self = shift;
return $self->{diameter} * 0.5;
}
sub get_circumference {
my $self = shift;
return $self->{diameter} * 3.14;
}
sub get_area {
my $self = shift;
my $area = $self->get_radius ** 2 * 3.14;
}
1;
Circle.pm文件应保存在Shape/Circle.pm目录下。'use parent Shape;'这行代码告诉Perl,Circle.pm继承自Shape类。Circle类现在继承了来自Shape的datetime和color方法,同时也提供了新的特定于圆形的方法。请注意,Circle.pm也继承了来自Shape.pm的'new'和'get_area'方法,但它通过在Circle.pm中重新定义它们来覆盖了这些方法。让我们使用Draw.pl测试Circle.pm。
use strict;
use warnings;
use feature qw/say/;
use Shape::Circle;
my $circle = Shape::Circle->new;
# get datetime - inherited method
say $circle->get_datetime;
# try new Circle methods
say $circle->get_radius;
say $circle->get_circumference;
say $circle->get_area;
总结
本文探讨了Perl的核心面向对象功能。有关更多信息,官方Perl文档有教程和更详细的参考。关于传统面向对象Perl的权威文本是Damian Conway的面向对象Perl(Manning,1999)。
本文最初发布在PerlTricks.com。
标签
反馈
这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。