5

Perlモジュールをオブジェクト指向の方法で構造化する方法を理解するのに苦労しているので、1つの親モジュールに複数のサブモジュールを含めることができ、必要な特定のサブモジュールのみが呼び出し元のスクリプトによってロードされます。たとえば、次のようなメソッド呼び出しを行えるようにしたいです。

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

4 に答える 4

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
}

上記の例では、パッケージ名とそれらが含まれていたファイルの間の2つの翻訳を示しました。通常、すべて::が変更さ/.pm、最後に追加されます。次に、perlはグローバル変数のすべてのディレクトリでそのファイルを検索します@INC。すべての詳細については、requireのドキュメントを参照してください。

アップデート:

このメソッドをキャッシュする1つの方法は、実行時に値を返すだけの関数に置き換えることです。

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

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

    $users
}
于 2010-08-29T00:31:52.340 に答える
1

これは、ロールを API ドライバー インスタンスに選択的に適用する大きな醜い Moose の例です。

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 サーバーに変換できます。

これが Moose に目を向けるきっかけになれば幸いです。ドキュメントは優れています。特に、マニュアルとクックブックを確認してください。

于 2010-08-29T07:18:17.810 に答える
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::FooExample::API::Foo::bar$api

あるいは最悪の場合、

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

このコードを呼び出す

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

この機能の使い方によっては、必要なものをすべてインポートするよりもはるかにオーバーヘッドがかかる場合があります。

于 2010-08-29T04:17:53.997 に答える
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 モジュール ディストリビューションの維持に関連する多くのタスクを処理する新しいツールです。驚くほど強力で柔軟です。しかし、それは新しく、ドキュメントは少し荒いです. そのパワーと柔軟性に伴う避けられない複雑さは、それを使用することを学ぶことがプロジェクトであることを意味します. とても面白そうに見えますが、まだ深く掘り下げる時間はありません。

ロードされるメソッドの数を制限する必要がある場合は、AutoLoader または SelfLoader を使用呼び出されたサブルーチンをロードできます。これにより、メソッドが初めて呼び出されるときにわずかなオーバーヘッドが発生します。私の経験では、このアプローチが必要になることはめったにありません。

最善の方法は、オブジェクトを小さくして厳密に定義し、単純な概念を具現化することです。あいまいさや中途半端な概念をオブジェクトに入れないようにしてください。代わりに、潜在的な混乱の領域を処理するために構成と委任を使用することを検討してください。たとえば、ユーザーの最終ログインを処理する日付フォーマット メソッドを追加する代わりに、DateTime オブジェクトを last_login 属性に割り当てます。

構成と委任を簡単にするために、 Mooseを使用してオブジェクトを作成することを検討してください。これは、Perl OOP とオブジェクトの構成と委任に関連する単調な作業の多くを取り除きます。

于 2010-08-29T05:49:13.960 に答える