设置Perl模块结构

时间:2010-08-28 23:18:56

标签: perl oop perl-module

我无法弄清楚如何以面向对象的方式构造Perl模块,因此我可以拥有一个包含多个子模块的父模块,并且只需要一个调用脚本加载所需的特定子模块。例如,我希望能够像这样进行方法调用:

use Example::API;    
my $api = Example::API->new();

my $user = {};
$user->{'id'} = '12345';

$api->Authenticate();
$user->{'info'} = $api->Users->Get($user->{'id'});
$user->{'friends'} = $api->Friends->Get($user->{'id'});

在文件结构方面,我希望将模块设置如下,或者以任何结构使一切正常工作:

api.pm
users.pm
friends.pm
...

我首先想要这样做的原因是,如果有人只想对API进行身份验证,则他们不必加载所有其他模块。同样,如果有人只想获取用户的信息,他们就不必加载friends.pm模块,只需加载users.pm。如果您能提供必要的示例Perl代码来设置每个模块以及解释如何设置文件结构,我将不胜感激。如果我想要完成我想要完成的任何事情,我会很感激地解释最好的方法和一些关于如何设置的示例代码。

4 个答案:

答案 0 :(得分:3)

从您的示例中,在您的主模块中,我假设您将提供访问器方法来获取子类。所以你要做的就是在该方法的顶部包含require Sub::Module;。编译时不会发生任何事情,但第一次运行代码时,perl将加载模块。第一次加载后,行require Sub::Module;将成为无操作。

如果所有代码都是面向对象的,则无需担心导入函数。但是,如果这样做,语句use Module qw(a b c);将被解释为:

BEGIN {
    require Module;
    Module->import(qw(a b c));
}

BEGIN使它在编译时发生,但没有什么能阻止你在运行时使用内部。您在运行时导入的任何子例程都必须使用括号调用,并且原型将不起作用,因此除非您知道自己在做什么,否则运行时导入可能是个坏主意。运行时require和通过包方法访问是完全安全的。

因此,您的$api->Users方法可能会起到以下作用:

# in package 'Example::API' in the file 'Example/API.pm'

sub Users {
    require Example::API::Users;  # loads the file 'Example/API/Users.pm'
    return  Example::API::Users->new( @_ ); # or any other arguments
}

在上面的示例中,我展示了软件包名称与其所在文件之间的两种翻译。通常,所有::都更改为/.pm添加到最后。然后perl将在全局变量@INC的所有目录中搜索该文件。您可以查看require的文档了解所有详细信息。

更新

缓存此方法的一种方法是在运行时使用只返回值的函数替换它:

sub Users {
    require Example::API::Users;
    my $users = Example::API::Users->new;

    no warnings 'redefine';
    *Users = sub {$users};

    $users
}

答案 1 :(得分:1)

这是一个非常丑陋的Moose示例,它有选择地将角色应用于API驱动程序实例。

script.pl

use Example::User;   

# User object creates and authenticates a default API object.
my $user = Example::User->new( id => '12345' );

# When user metadata is accessed, we automatically
# * Load the API driver code.
# * Get the data and make it available.    
print "User phone number is: ", $user->phone_number, "\n";

# Same thing with Friends.
print "User has ", $user->count_friends, " friends\n";

print "User never logged in\n" unless $user->has_logged_in;

Example / API.pm - 基本协议驱动程序类:

package Example::API;

use Moose;

has 'host' => (
    is => 'ro',
    default => '127.0.0.1',
);

sub Authenticate {

   return 1;

}

# Load the user metadata API driver if needed.
# Load user metadata
sub GetUserInfo {
    my $self = shift;

    require Example::API::Role::UserInfo;

    Example::API::Role::UserInfo->meta->apply($self) 
        unless $self->does('Example::API::Role::UserInfo');

    $self->_Get_UserInfo(@_);
}

# Load the friends API driver if needed.
# Load friends data and return an array ref of Friend objects
sub GetFriends {
    my $self = shift;

    #require Example::API::Role::Friends;

    Example::API::Role::Friends->meta->apply($self) 
        unless $self->does('Example::API::Role::Friends');

    $self->_Get_Friends(@_);
}

用户元数据和朋友数据驱动程序构建为“角色”,可根据需要动态应用于API驱动程序实例。

实施例/ API /角色/ UserInfo.pm:

package Example::API::Role::UserInfo;

use Moose::Role;

sub _Get_UserInfo {
    my $self = shift;
    my $id = shift;

    my $ui = Example::API::User::MetaData->new(
        name => 'Joe-' . int rand 100,
        phone_number => int rand 999999,
    );

    return $ui;
}

实施例/ API /角色/ Friends.pm:

use Moose::Role;

sub _Get_Friends {
    my $self = shift;
    my $id = shift;

    my @friends = map {
        Example::API::Friend->new( 
            friend_id => "$id-$_", 
            name => 'John Smith'
        );
    } 1 .. (1 + int rand(5));

    return \@friends;
}

朋友对象:

实施例/ API / Friend.pm

package Example::API::Friend;

use Moose;

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

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

用户元数据对象。

实施例/ API /用户/ MetaData.pm

package Example::API::User::MetaData;

use Moose;

has 'name' => (
    is => 'ro',
    isa => 'Str',
);
has 'phone_number' => (
    is => 'ro',
    isa => 'Str',
);
has 'last_login' => (
    is => 'ro',
    isa => 'DateTime',
    predicate => 'has_logged_in',
);

终于用户对象。我已经使用了许多Moose功能,只需要少量的命令式代码即可使其成为一个非常强大的对象。

package Example::User;

use Moose;

has 'id' => (
    is => 'ro',
    isa => 'Int',
    required => 1,
);
has 'server_connection' => (
    is      => 'ro',
    isa     => 'Example::API',
    builder => '_build_server_connection',
);

# Work with a collection of friend objects.
has 'friends' => (
    is => 'ro',
    isa => 'ArrayRef[Example::API::Friend]',
    traits    => ['Array'],
    handles   => {
        all_friends    => 'elements',
        map_friends    => 'map',
        filter_friends => 'grep',
        find_option    => 'first',
        get_option     => 'get',
        join_friends   => 'join',
        count_friends  => 'count',
        has_no_friends => 'is_empty',
        sorted_friends => 'sort',
    },
    lazy_build => 1,
);

has 'user_info' => (
    is => 'ro',
    isa => 'Example::API::User::MetaData',
    handles   => {
        name => 'name',
        last_login => 'last_login',
        phone_number => 'phone_number',
        has_logged_in => 'has_logged_in',
    },
    lazy_build => 1,
);

sub _build_server_connection {
    my $api =  Example::API->new();
    $api->Authenticate();

    return $api;
}

sub _build_friends {
    my $self = shift;

    $self->server_connection->GetFriends( $self->id );
}

sub _build_user_info {
    my $self = shift;

    $self->server_connection->GetUserInfo( $self->id );
}

这个例子充分利用了很多Moose魔法,但是对于那些使用这些对象的人来说,你最终会得到一个非常简单的界面。虽然这接近200行格式化代码,但我们完成了大量工作。

添加类型强制可以提供更简单的界面。原始字符串日期可以自动解析为DateTime对象。原始IP地址和服务器名称可以转换为API服务器。

我希望这能激发你去看看穆斯。文档 excellect ,特别是查看手册和Cookbook。

答案 2 :(得分:0)

管理导出很棘手,但您可以使用AUTOLOAD解决此问题的方法。如果perl无法识别您尝试调用的子例程名称,则可以将其传递给名为AUTOLOAD的子句。假设我们这样做了:

use Example::API;

sub AUTOLOAD {
    my $api = shift;
    eval "require $AUTOLOAD"; # $api->Foo->... sets $AUTOLOAD to "Example::API::Foo"
    die $@ if $@;             # fail if no Example::API::Foo package
    $api;
}

然后这段代码:

$api = new Example::API;
$api->Foo->bar(@args);

将(假设我们尚未首先导入Example::API::Foo)调用我们的AUTOLOAD方法,尝试加载Example::API::Foo模块,然后尝试调用方法Example::API::Foo::bar使用$api对象和您提供的其他参数。

或者在最坏的情况下,

$api->Foo->bar(@args)

导致调用此代码

eval "require Example::API::Foo";
die $@ if $@;
&Example::API::Foo::bar($api,@args);

根据您使用此功能的方式,它可能比仅导入您需要的所有内容要多得多。

答案 3 :(得分:0)

有许多工具可用于快速构建新模块开发的骨架结构。

  • h2xs附带标准的Perl发行版。它的主要重点是构建用于与C库连接的XS代码。但是,它确实为布置纯Perl项目提供了基本支持:h2xs -AX --skip-exporter -n Example::API

  • 我使用Module::Starter为我的模块开发构建开始布局。它做了很多h2xs没做的事情。 module-starter --module=Example::API,Example::Friends,Example::Users --author="Russel C" --email=russel@example.com

  • Dist::Zilla是一个新工具,可以处理与维护Perl模块分发相关的许多任务。它非常强大而且灵活。但这是新的,文档有点粗糙。所有功能和灵活性带来的不可避免的复杂性意味着学习使用它是一个项目。它看起来很有趣,但我没有花时间潜入,但是。

如果您需要限制加载的方法数,可以使用AutoLoaderSelfLoader加载子程序。当第一次调用方法时,这将导致轻微的开销。根据我的经验,很少需要这种方法。

最好的办法是保持对象小而严格定义,以便它们体现一个简单的概念。不要让歧义或中途概念进入您的对象,而是考虑使用组合和委托来处理可能混淆的区域。例如,不是添加日期格式化方法来处理用户的上次登录,而是将DateTime对象分配给last_login属性。

为了简化合成和委派,请考虑使用Moose来构建对象。它消除了Perl OOP中涉及的大部分苦差事,以及特定的对象组合和委派。