Настройка структуры модуля Perl - PullRequest
4 голосов
/ 29 августа 2010

У меня возникают проблемы с выяснением того, как структурировать модули 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 ]

3 голосов
/ 29 августа 2010

Из вашего примера, в вашем основном модуле я предполагаю, что вы будете предоставлять методы доступа для доступа к подклассам.Поэтому все, что вам нужно сделать, это включить require Sub::Module; в начале этого метода.Во время компиляции ничего не произойдет, но при первом запуске кода Perl загрузит модуль.После первой загрузки строка require Sub::Module; станет недоступной.

Если весь ваш код ориентирован на объекты, вам не нужно беспокоиться об импорте функций.Но если вы это сделаете, выражение use Module qw(a b c); будет интерпретировано как:

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

BEGIN, и это произойдет во время компиляции, но ничто не мешает вам использовать внутренние компоненты во время выполнения.Любые подпрограммы, которые вы импортируете во время выполнения, должны вызываться в круглых скобках, и прототипы работать не будут, поэтому, если вы не знаете, что делаете, импорт во время выполнения, вероятно, плохая идея.Время выполнения require s и доступ через методы пакета полностью безопасны.

Так что ваш метод $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 голос
/ 29 августа 2010

Вот большой уродливый пример 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 );
}

В этом примере используется много магии лося, но вы получите очень простой интерфейс для тех, кто использует объекты. Хотя это близко к 200 строкам отформатированного кода, мы выполнили огромное количество.

Добавление приведения типов даст еще более простой интерфейс. Необработанные строковые даты могут автоматически анализироваться в объектах DateTime. Необработанные IP-адреса и имена серверов могут быть преобразованы в серверы API.

Надеюсь, это вдохновит вас взглянуть на Муз. Документация: excellect , ознакомьтесь, в частности, с Руководством и Поваренными книгами.

0 голосов
/ 29 августа 2010

Существует ряд инструментов, которые можно использовать для быстрого построения скелетной структуры для разработки вашего нового модуля.

  • h2xs поставляется со стандартным дистрибутивом Perl.Основное внимание уделяется созданию кода 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, а также композицию и делегирование объектов.

0 голосов
/ 29 августа 2010

Управлять экспортом сложно, но вы можете использовать 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);

В зависимости от того, как вы используете эту функцию, это может быть намного больше, чем просто импортировать все, что вам нужно.

...