在perl中,如何在多个包中编写方法?

In perl, how can I write methods in multiple packages?

我正在写一个 perl class,出于这个问题的目的,我将其称为 Student。 Studentclass会有很多方法,为了整洁和效率,我不想把它们都放在一个源文件中。

假设 Student.pm 是 package Student 并且包含 sub new 构造函数,而 Student/Enroll.pm 是 package Student::Enroll 并且包含 sub enroll,一个方法。我这样写 sub enroll:

sub enroll
{
    my ($student) = @_;

    # do something;
    return;
}

*Student::enroll = \&enroll;

在我的主程序中,我可以写my $student = new Student()$student->enroll()。这有效,但它是不透明的。它有任何我忽略的缺点吗?

我还没有尝试将包 Student::Enroll 中的 enroll 导出到包 Student 中。这不是我想要的,因为那时我需要在 Student 包中编写 use Student::Enroll,而我不想那样做。会有需要 Student 包但不需要的程序 Student::Enroll.

在Student/Enroll.pm 中是否有更好、更简洁的方法来编写sub enroll,使其成为Student 方法?

您可以在 sub 声明中使用包限定符

package Student::Enroll;
...
sub foo { ... }   # Student::Enroll::foo
sub Student::enroll { ... }  # Student::enroll, not Student::Enroll::enroll
...

当然如果在Student/Enroll.pm中只定义了Student::enroll,而某些程序需要Student::enroll函数,那么该程序将不得不加载Student/Enroll.pm和所有的Student::Enroll 包。

借助 AUTOLOAD 方法可以轻松完成。

package Student::Enroll;
use strict;
use warnings;
use v5.14;

sub enroll { say "Good luck $_[1]";  }

1;

新版本:

package Student;
use strict;
use warnings;
use v5.14;
use Carp qw/croak/;

{
    my $_additional_methods = {
        enroll => undef
    };

    sub _can_access { 
        exists $_additional_methods->{$_[0]} 
    }
}

sub new { bless {}, shift }

# Subroutine AUTOLOAD will be called always 
# when someone calls a method (a subroutine) which 
# isn't present in this module.
# In our case "enroll" will be called from AUTOLOAD
# only one and next time of calling it will be called
# directly as a normal method of the class since
# we create a typeglob ref for it.
sub AUTOLOAD {
    no strict 'refs';

    our $AUTOLOAD; # in this global variables are kept
                   # the full name of a called method

    my $package_method = $AUTOLOAD;                   

    # making up the full method name
    $AUTOLOAD =~ s/(.*)::(\w+)$/Student::Enroll::/;

    my $package = ;
    my $method  = ;

    if ( _can_access($method) ) { 

        eval 'use Student::Enroll;'; # loading of the necessary class

        &$AUTOLOAD(@_); # calling the method from Student::Enroll class

        no warnings 'redefine';                    
        *{$package_method} = \&AUTOLOAD;
    } else {
        croak "Can't locate object method '$method' via package '$package'";
    }
}

package main;
use strict;
use warnings;

my $student = Student->new;
$student->enroll('David');
$student->enroll('David');
$student->enoll('David');

旧版本:

package Student;
use strict;
use warnings;
use v5.14;

sub new { bless {}, shift }

# Subroutine AUTOLOAD will be called always 
# when someone calls a method (a subroutine) which 
# isn't present in this module (enroll in our case)
sub AUTOLOAD {
    no strict 'refs';

    our $AUTOLOAD; # in this global variables are kept
                   # the full name of a called method

    eval 'use Student::Enroll;'; # loading of the necessary class

    # making up the full method name
    $AUTOLOAD =~ s/.*::(\w+)$/Student::Enroll::/;
    &$AUTOLOAD; # calling the method from Student::Enroll class
                # Note: array @_ of args is passed automatically
}

package main;
use strict;
use warnings;

my $student = Student->new;
$student->enroll('David');

首先,我建议以 'Modern Perl' 的方式开始编程,并远离老式的 Perl5 blessed hashes for object...

use Moose;

这是一个适合 Perl 的 OO 系统,借鉴自 Perl6。

当您的 class 变成 'big' 时,是时候重新考虑 class 中实际包含的内容以及不应该包含的内容了。某些属性和方法实际上可能非常通用,可以从超级 class 继承。但是 Moose(和 Moo)也有 'Roles',可以添加到另一个 class.

之上的行为

让我展示一下我的 'Student' class 的样子:

package College::Student;

use Moose;

extends 'Person';

has 'student_registration_number' => (
    is       => 'ro',
    isa      => 'Int',
    required => 1,
);

with 'College::Enrolment';

1;

发生了什么:

第 01 行:声明包的名称,College::Student,大学相关内容的单独命名空间。

第 03 行:use Moose;自然!

第 05 行:我们正在使用另一个 class、Person 并将其用作基础 class。现在,在这种情况下,我确实创建了一个 College::Student class,一个更具体的 class,然后是一个通用的 Person class(不在 College 命名空间)。

第07行:具体到这个College::Studentclass,只是这里我们用了一个student_registration_number,它是一个整数类型的只读属性,是必须的。

第 12 行:对于 College::Enrolment 这个角色,我们希望学生有额外的行为 - 这样它可能可以注册一门课程,以及为了做到这一点需要什么方法和属性,它们是在这里并不重要,我们在这里要关心的是它能做到这一点。

现在简单看一下'College::Enrolment' class:

package College::Enrolment;

use Moose::Role;

sub enroll {
    my $self = shift; # a person
    my $args = {@_};
    print 
        $self->name,
        " is being enrolled into: ",
        $args->{'course'}->course_title,
        "\n",
    ;   
};

sub un_enroll {
};

1;

第 01 行:一个不错的命名空间,又与大学有关。

第 03 行:这是一个可以应用于其他对象的 Moose::Role...您不能实例化这些对象!

第05行:啊...显然,有一个enroll实例方法,

第 16 行:还有一个 un_enroll 实例方法。

为了完整起见,基础 class Person 看起来很基础,没有针对学生的具体内容。

package Person;

use Moose;

has name => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has date_of_birth => (
    is       => 'ro',
    isa      => 'DateTime',
    required => 1,
);

1;

也可以将此 class 扩展为 College::Professor,例如:

package College::Professor;

use Moose;

extends 'Person';

has 'employee_number' => (
    is       => 'ro',
    isa      => 'Int',
    required => 1,
);

with 'College::Enrolment'

1;

现在,College::ProfessorPerson,但是它没有 College::Enrolment 角色。

如果您愿意,为 College::GuestStudent 设置一个 class 也很容易,它可能没有注册号,但确实需要能够注册 --- 同样角色。

这里开始发挥 Moose Roles 的作用...

与其创建巨大的 classes,不如尝试拆分尽可能多的角色,这些角色是方法及其属性的逻辑组合。这使它更易于维护和测试。很快您就会发现角色是构建 classes 的一种更合乎逻辑的方式,而不是尝试继承 - 或者更糟的是多重继承。

哦..少了点东西:

use strict;
use warnings;

use College::Student;
use College::Course;

use DateTime::Format::ISO8601;

my $study = College::Course->new(
    course_title  => "French for beginners"
);

my $pupil = College::Student->new(
    name          => "John Doe",
    date_of_birth => DateTime::Format::ISO8601->parse_datetime("2001-07-10"),
    student_registration_number
                  => '123456',

);

$pupil->enroll( course => $study );


__END__

别忘了在

阅读有关 Moose 的文章

http://modernperlbooks.com/books/modern_perl_2014/07-object-oriented-perl.html

http://www.theperlreview.com/articles/moose.html