Skip to content

Perl 面向对象

Perl 支持面向对象编程(Object-Oriented Programming,OOP)。它的 OO 系统灵活,基于包(packages)、引用(references)和一个称为 bless 的特殊函数。虽然 Perl 内建的 OO 系统功能强大,但现代 Perl 通常使用像 Moo、Moose 或 Object::Pad 这样的系统来提供更多特性和语法糖。

  • 对象(Object): 在 Perl 中,对象就是一个引用(通常是哈希或数组的引用),它被“祝福(bless)”到了一个类中。祝福将引用与包名关联起来,使其成为该类的一个实例。
  • 类(Class): 在 Perl 中,类就是一个包(package)。这个包包含了操作该类对象的方法(methods),方法实际上就是包内的子例程(subroutines)。
  • 方法(Method): 方法是在类(包)中定义的子例程。方法的第一个参数始终是对象本身(如果通过对象调用,例如 $obj->method())或类名(如果作为类方法调用,例如 ClassName->method())。这个第一个参数通常被称为“调用者(invocant)”。
  • 祝福(Blessing): bless 函数用于将一个普通引用转换为对象。bless $reference, $className; 将 $reference 与 $className 关联起来。

使用 package 关键字定义类。方法是这个包内的子例程。

# File: Person.pm
package Person;
use strict;
use warnings;
# Constructor
sub new {
my ($class, %args) = @_;
my $self = {
firstName => $args{firstName} // 'N/A', # Using defined-or for defaults
lastName => $args{lastName} // 'N/A',
ssn => $args{ssn} // '000-00-0000',
};
bless $self, $class; # Make $self an object of class $class (Person)
return $self;
}
# Accessor method (getter)
sub get_full_name {
my ($self) = @_;
return $self->{firstName} . " " . $self->{lastName};
}
# Mutator method (setter)
sub set_ssn {
my ($self, $new_ssn) = @_;
if (defined $new_ssn) {
# Basic validation could go here
$self->{ssn} = $new_ssn;
}
return $self->{ssn};
}
1; # Modules must return a true value

要创建类的一个实例(即对象),你需要调用其构造方法,按照惯例通常命名为 new。

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin); # To find the script's directory
use lib $Bin; # Add it to @INC to find Person.pm
use Person; # Load the Person class
# Create a Person object
my $person1 = Person->new(
firstName => "Jane",
lastName => "Doe",
ssn => "123-45-6789"
);
# Call methods on the object
print "Person 1: ", $person1->get_full_name(), "\n";
print "Person 1 SSN (initial): ", $person1->{ssn}, "\n"; # Direct access (discouraged for encapsulation)
$person1->set_ssn("987-65-4321");
print "Person 1 SSN (updated): ", $person1->{ssn}, "\n";
my $person2 = Person->new(firstName => "John");
print "Person 2: ", $person2->get_full_name(), " (SSN: ", $person2->{ssn}, ")\n";

脚本输出:

Person 1: Jane Doe
Person 1 SSN (initial): 123-45-6789
Person 1 SSN (updated): 987-65-4321
Person 2: John N/A (SSN: 000-00-0000)

Perl 的基本 OO 系统不强制执行数据隐私(例如,私有属性)。对象属性(在我们示例中是哈希键)可以直接访问。然而,最佳实践是使用访问器(getter)和修改器(setter)方法来与对象数据交互。这促进了封装(encapsulation),允许对象内部表示的改变,而不会破坏使用该类的代码。

在 Person.pm 示例中,get_full_name 是一个 getter,set_ssn 是一个 setter。使用前导下划线命名属性(例如 _firstName)是一种常见的约定,表示它们 intended for internal use,但 Perl 不强制执行此约定。

继承允许一个类(子类,subclass 或 child class)继承另一个类(超类,superclass 或 parent class)的方法和属性。Perl 通过特殊的包数组 @ISA 实现继承。

要让类 Employee 继承自 Person,需要设置 Employee 的 @ISA 数组:

# File: Employee.pm
package Employee;
use strict;
use warnings;
use Person; # We need the Person class definition
our @ISA = qw(Person); # Employee ISA Person (inherits from Person)
# Constructor for Employee
sub new {
my ($class, %args) = @_;
# Call parent's constructor
my $self = $class->SUPER::new(
firstName => $args{firstName},
lastName => $args{lastName},
ssn => $args{ssn}
);
# Add Employee-specific attributes
$self->{employeeID} = $args{employeeID} // 'EMP-0000';
$self->{department} = $args{department} // 'Unassigned';
# Re-bless into the current class, though SUPER::new already blessed into Person.
# For simple inheritance, this bless might not be strictly necessary if SUPER::new
# handles the class correctly, but often good for clarity or complex hierarchies.
bless $self, $class;
return $self;
}
# Employee-specific method
sub get_employee_details {
my ($self) = @_;
return "ID: " . $self->{employeeID} . ", Name: " . $self->get_full_name() . ", Dept: " . $self->{department};
}
# Method Overriding: Provide a specialized version of a parent method
sub get_full_name {
my ($self) = @_;
# Call parent's get_full_name and append title
my $fullName = $self->SUPER::get_full_name();
return "Emp. " . $fullName;
}
1;

使用 Employee 类:

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin);
use lib $Bin;
use Employee;
my $emp1 = Employee->new(
firstName => "Alice",
lastName => "Smith",
ssn => "111-22-3333",
employeeID => "EMP-101",
department => "Engineering"
);
print $emp1->get_employee_details(), "\n";
print "Full name (overridden): ", $emp1->get_full_name(), "\n";
# Calling an inherited method (set_ssn is from Person)
$emp1->set_ssn("444-55-6666");
print "Updated SSN: ", $emp1->{ssn}, "\n";

输出:

ID: EMP-101, Name: Emp. Alice Smith, Dept: Engineering
Full name (overridden): Emp. Alice Smith
Updated SSN: 444-55-6666
  • Perl 首先在对象的自身类中查找方法。
  • 如果未找到,它会按照 @ISA 中列出的类进行查找,遵循深度优先、从左到右的顺序。
  • SUPER:: 伪类(pseudo-class)允许子类调用其超类中被重写(override)的方法。

子类可以提供其超类中已定义的方法的自己的实现。这称为方法重写或方法覆盖。上面示例中的 Employee 类重写了 Person 类中的 get_full_name 方法。

如果在对象(或类)上调用的方法在其类或任何超类中不存在,Perl 会查找一个名为 AUTOLOAD 的特殊方法。如果定义了 AUTOLOAD,则会使用原始参数调用它。缺失方法的完整限定名称(fully qualified name)可以在包变量 $AUTOLOAD 中获得。

虽然 AUTOLOAD 可用于在运行时创建方法等任务,但它会使代码更难调试和理解。请谨慎使用。

# Inside a class definition
# sub AUTOLOAD {
# my $self = shift;
# my $method_name = $AUTOLOAD;
# $method_name =~ s/.*:://; # Strip package name
# warn "Called undefined method: $method_name\n";
# # ... try to handle it ...
# }

Perl 使用基于引用计数(reference counting)的自动垃圾回收(garbage collection)。当一个对象不再被引用(例如,其变量超出作用域且没有其他引用指向它),Perl 会回收其内存。

如果你需要在对象即将被销毁时执行特定的清理操作(例如,关闭文件句柄、释放资源),可以定义一个名为 DESTROY 的特殊方法。这个析构方法(destructor method)会在对象内存被回收前自动由 Perl 调用。

# Inside a class definition (e.g., Person.pm)
# sub DESTROY {
# my ($self) = @_;
# print "Person object '", $self->get_full_name(), "' is being destroyed.\n";
# # ... perform cleanup actions ...
# }
#
# # Example of when DESTROY is called:
# {
# my $scoped_person = Person->new(firstName => "Temp");
# # $scoped_person's DESTROY will be called when it goes out of scope here
# }
#
# my $global_person = Person->new(firstName => "Global");
# undef $global_person; # DESTROY called here
# # Or DESTROY called when script ends if $global_person still exists

虽然理解 Perl 内建的 OO 系统很重要,但许多现代 Perl 项目使用 CPAN 提供的 OO 系统,例如:

  • Moo: 轻量、快速且现代的 OO 系统,提供了属性、类型约束和角色等特性,模板代码量极少。
  • Moose: 一个高度全面、后现代的 OO 系统,具有丰富的特性,包括角色、类型系统、内省(introspection)和扩展生态系统。它非常强大,但启动开销可能更高。
  • Object::Pad: 一个较新的系统(需要 Perl 5.30+),旨在通过关键字插件在 Perl 核心语法中直接提供更传统的 OO 语法(例如 class、has、method 关键字)和更好的封装。

这些系统解决了 Perl 基本 OO 的一些冗余和限制,使 OO 开发更健壮和易于维护。