强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Perl 完全指南 / 第 11 章:面向对象编程

第 11 章:面向对象编程

“Perl 的 OOP 从 bless 开始,到 Moose/Moo 升华”

Perl 的面向对象系统是基于 bless 构建的。虽然原始语法看起来简陋,但现代 Perl 推荐使用 MooseMoo 框架。


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 对比

特性MooseMoo
启动速度快(约 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;

本章小结

要点内容
blessPerl 原始 OOP 的基础
new构造函数通常使用 bless {}, $class
use parent推荐的继承方式
Moose功能完整的 OOP 框架(启动慢)
Moo轻量级 Moose 替代(启动快)
classPerl 5.38+ 原生 OOP 语法
Role组合优于继承的设计模式

练习

  1. bless 实现一个 Counter 类(inc、dec、value 方法)
  2. 用 Moose 实现一个 BankAccount 类(存款、取款、余额查询)
  3. 创建一个 Logger Role,提供 log 方法
  4. 用 Moo 实现一个 Circle 类,计算面积和周长
  5. 尝试 Perl 5.38 的 class 语法(如果版本支持)

扩展阅读