Введение


Moose — библиотека для работы с объектами в Perl. Общую информацию о ней можно получить, например, здесь.

Как можно видеть из комментариев к статьей по ссылке выше, Moose — продукт несколько противоречивый. Дело в том, что Moose — это не просто реализация ООП для Perl, это реализация ООП с местным колоритом. Об этом колорите далее и будет идти речь.

Поскольку Moose — несколько нетипичная ООП-библиотека, то не все стандартные приемы и ООП-паттерны хорошо на него ложатся. Напротив, некоторые приемы, не свойственные другим языкам, прекрасно на него ложатся.

Я приведу прием, который я и мои коллеги используем в реальных приложениях. Я дал ему условное название «цепочка билдеров» скорей потому, что мне не пришло в голову ничего лучше, а не потому, что это название мне действительно нравится.

Цепочка билдеров


Moose позволяет объявлять ленивые атрибуты, т. е. атрибуты с отложенной инициализацией. Также для каждого может быть объявлена функция-инициализатор (она же билдер).

has xml => (
    is => 'ro',
    isa => 'Str',
    lazy => 1,
    builder => '_build_xml',
);


Использование подобной комбинации не редкость, поэтому для нее существует специальное сокращение:

has xml => (
    is => 'ro',
    isa => 'Str',
    lazy_build => 1,
);


Подобная возможность позволяет использовать следующее интересное решение. В случае, если от объекта требуется проделать некоторый набор последовательных действий, то этот набор может быть представлен в виде цепочки атрибутов с билдерами.

Вот, например, набросок класса, который скачивает по TCP XML-файл с данными и выдает некоторую сводку.

package TotalCalculator;
use Moose;

has remote_host => (...);
has tcp_connection => (..., lazy_build => 1);
has xml => (..., lazy_build => 1);
has data => (..., lazy_build => 1);
has total => (..., lazy_build => 1);

sub _build_tcp_connection {
    my ($self) = @_;
    return Connection->new($self->remote_host);
}
sub _build_xml {
    my ($self) = @_;
    return $self->connection->get_text;
}
sub _build_data {
    my ($self) = @_;
    return parse_xml($self->xml);
}
sub _build_total {
    my ($self) = @_;
    return $self->data->{A} + $self->data->{B};
}

sub get_total {
    my ($self) = @_;
    return $self->total;
}


Что мы получаем? Пользователь такого класса может инстанцировать объект разными способами в зависимости от того, каким данным он уже обладает. В самом простом случае ему достаточно указать адрес удаленного хоста, но если у него уже есть XML, то он может указать его при инстанцировании, и код, отвечающий за получение его по TCP, вообще не будет выполнен:

my $total = TotalCalculator->new(xml => $xml)->get_total;


При этом сам код класса получается весьма изящным, с легким налетом функционального подхода.

Еще пара слов


Данный прием мне кажется интересным как сам по себе, так и как пример нетипичного, но все же красивого и надежного решения. И своим рождением не в последнюю очередь он обязан тому самому колориту в Moose, о котором я говорил в начале.