Введение
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, о котором я говорил в начале.