Perl 完全指南 / 第 11 章:面向对象编程
第 11 章:面向对象编程
“Perl 的 OOP 从 bless 开始,到 Moose/Moo 升华”
Perl 的面向对象系统是基于 bless 构建的。虽然原始语法看起来简陋,但现代 Perl 推荐使用 Moose 或 Moo 框架。
11.1 Perl OOP 基础 — bless
#!/usr/bin/env perl
use strict;
use warnings;
package Animal;
# 构造函数
sub new {
my ($class, %args) = @_;
my $self = {
name => $args{name} // "Unknown",
sound => $args$sound // "...",
};
return bless $self, $class; # 将哈希引用祝福为对象
}
# 方法
sub name { $_[0]->{name} }
sub sound { $_[0]->{sound} }
sub speak {
my ($self) = @_;
print $self->name . " says " . $self->sound . "!\n";
}
package main;
my $cat = Animal->new(name => "Kitty", sound => "Meow");
$cat->speak(); # Kitty says Meow!
bless 的含义
# bless 将一个引用与一个包关联起来
my $data = { name => "Perl" };
bless $data, "Animal";
# 现在 $data 是一个 Animal 对象
print ref($data), "\n"; # Animal
print $data->name(), "\n"; # 调用 Animal::name 方法
11.2 方法调用
# 对象方法调用(第一个参数是对象)
$object->method(@args);
# 等价于
Animal::method($object, @args);
# 类方法调用(第一个参数是类名)
Animal->new(@args);
# 等价于
Animal::new("Animal", @args);
11.3 继承
package Dog;
use parent 'Animal'; # 继承 Animal(推荐)
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args); # 调用父类构造函数
$self->{loyalty} = 100;
return $self;
}
# 覆写方法
sub speak {
my ($self) = @_;
print $self->name . " barks: Woof! Woof!\n";
}
sub fetch {
my ($self, $item) = @_;
print $self->name . " fetches the $item!\n";
}
package main;
my $dog = Dog->new(name => "Rex", sound => "Woof");
$dog->speak(); # Rex barks: Woof! Woof!
$dog->fetch("ball"); # Rex fetches the ball!
@ISA 和方法解析
# @ISA 定义继承关系(旧写法)
package Dog;
our @ISA = ('Animal');
# 推荐使用 parent 模块
use parent 'Animal';
# 方法解析顺序:当前类 → @ISA → ...
# 可以用 mro 模块查看
use mro;
print join " → ", @{mro::get_linear_isa('Dog')};
# Dog → Animal → UNIVERSAL
11.4 属性访问器
package Person;
sub new {
my ($class, %args) = @_;
return bless {
name => $args{name} // "",
age => $args{age} // 0,
}, $class;
}
# 手写访问器
sub name {
my ($self) = @_;
return $self->{name};
}
sub set_name {
my ($self, $value) = @_;
$self->{name} = $value;
}
# 可读写访问器
sub age {
my ($self, $value) = @_;
if (defined $value) {
$self->{age} = $value;
}
return $self->{age};
}
11.5 Moose — 现代 Perl OOP
Moose 是 Perl 最强大的 OOP 框架,借鉴了 Perl 6(Raku)的对象系统。
package Person;
use Moose;
use namespace::autoclean;
# 属性声明
has 'name' => (
is => 'ro', # 只读 (read-only)
isa => 'Str', # 类型约束
required => 1, # 必填
);
has 'age' => (
is => 'rw', # 读写 (read-write)
isa => 'Int',
default => 0,
);
has 'email' => (
is => 'rw',
isa => 'Str',
predicate => 'has_email', # 自动生成 has_email 方法
clearer => 'clear_email', # 自动生成 clear_email 方法
);
has 'friends' => (
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub { [] },
);
# 方法
sub greet {
my ($self) = @_;
return "Hi, I'm " . $self->name . ", age " . $self->age;
}
# 构建时执行
sub BUILD {
my ($self) = @_;
print "Created person: " . $self->name . "\n";
}
__PACKAGE__->meta->make_immutable; # 优化性能
1;
使用 Moose 对象
use Person;
my $p = Person->new(name => "张三", age => 30);
print $p->greet(), "\n";
# $p->name("新名字"); # 错误!name 是只读的
$p->age(31); # 可以,age 是读写的
Moose 类型约束
use Moose::Util::TypeConstraints;
subtype 'PositiveInt',
as 'Int',
where { $_ > 0 },
message { "必须是正整数" };
coerce 'PositiveInt',
from 'Str',
via { int($_) };
has 'score' => (
is => 'rw',
isa => 'PositiveInt',
);
角色(Role)
package Printable;
use Moose::Role;
requires 'name'; # 要求使用此角色的类必须实现 name 方法
sub print_info {
my ($self) = @_;
print "Object: " . $self->name . "\n";
}
package Serializable;
use Moose::Role;
sub to_json {
my ($self) = @_;
require JSON::XS;
return JSON::XS::encode_json({ name => $self->name });
}
# 在类中使用角色
package Employee;
use Moose;
with 'Printable', 'Serializable';
has 'name' => (is => 'ro', isa => 'Str');
__PACKAGE__->meta->make_immutable;
1;
11.6 Moo — 轻量级 Moose
Moo 是 Moose 的轻量替代,启动速度更快,兼容 Moose API:
package Point;
use Moo;
use Types::Standard qw(Num);
has 'x' => (
is => 'rw',
isa => Num,
default => 0,
);
has 'y' => (
is => 'rw',
isa => Num,
default => 0,
);
sub distance_to {
my ($self, $other) = @_;
return sqrt(
($self->x - $other->x) ** 2 +
($self->y - $other->y) ** 2
);
}
1;
Moose vs Moo 对比
| 特性 | Moose | Moo |
|---|---|---|
| 启动速度 | 慢 | 快(约 10 倍) |
| 类型系统 | 内置 | 需要 Types::Standard |
| 元对象协议 | 完整 | 简化 |
| 角色(Role) | 完整 | 完整 |
| 内存占用 | 较高 | 较低 |
| 推荐场景 | 大型应用、需要元编程 | 工具模块、命令行工具 |
| CPAN 依赖 | 较多 | 较少 |
11.7 Perl 5.38+ class 语法
Perl 5.38 引入了原生的 class 关键字:
use feature 'class'; # Perl 5.38+
class Point {
field $x :param = 0;
field $y :param = 0;
method distance_to ($other) {
return sqrt(
($x - $other->x) ** 2 +
($y - $other->y) ** 2
);
}
method to_string () {
return "($x, $y)";
}
}
# 继承
class Point3D :isa(Point) {
field $z :param = 0;
method to_string () {
return "(" . $self->SUPER::to_string() . ", $z)";
}
}
my $p = Point->new(x => 3, y => 4);
print $p->to_string(), "\n"; # (3, 4)
11.8 业务场景:数据模型层
package Model::User;
use Moo;
use Types::Standard qw(Str Int ArrayRef);
use namespace::autoclean;
has 'id' => (
is => 'ro',
isa => Int,
);
has 'username' => (
is => 'ro',
isa => Str,
required => 1,
);
has 'email' => (
is => 'rw',
isa => Str,
);
has 'roles' => (
is => 'ro',
isa => ArrayRef[Str],
default => sub { ['user'] },
);
sub has_role {
my ($self, $role) = @_;
return grep { $_ eq $role } @{$self->roles};
}
sub to_hash {
my ($self) = @_;
return {
id => $self->id,
username => $self->username,
email => $self->email,
roles => $self->roles,
};
}
__PACKAGE__->meta->make_immutable;
1;
本章小结
| 要点 | 内容 |
|---|---|
bless | Perl 原始 OOP 的基础 |
new | 构造函数通常使用 bless {}, $class |
use parent | 推荐的继承方式 |
| Moose | 功能完整的 OOP 框架(启动慢) |
| Moo | 轻量级 Moose 替代(启动快) |
class | Perl 5.38+ 原生 OOP 语法 |
| Role | 组合优于继承的设计模式 |
练习
- 用
bless实现一个Counter类(inc、dec、value 方法) - 用 Moose 实现一个
BankAccount类(存款、取款、余额查询) - 创建一个
LoggerRole,提供 log 方法 - 用 Moo 实现一个
Circle类,计算面积和周长 - 尝试 Perl 5.38 的
class语法(如果版本支持)