Что такое Moose в perl. Для чего нужен Moose. Как его использовать. Функции Moose. Классы, роли, атрибуты, объекты Moose и т.д. Введение в Moose для начинающих.
Moose переводится с английского как «американский лось». Это название идет к нему как нельзя лучше. Большая и суровая корова. Молока не будет, по рогам получите. Ну вы поняли, как я к нему отношусь :)

Общепринятое определение: Moose — это расширение для Perl 5, которое позволяет упростить реализацию ООП.
Moose позволяет создавать простые классы без написания подпрограмм и упрощать сложные классы. Moose позволяет использовать такие новые для perl понятия, как роли, интерфейсы и атрибуты. Можно использовать метаклассы. Moose имеет множество расширений, которые распространяются в модулях MooseX .
Лично мое мнение: Moose имеет слишком чужеродный для perl синтаксис и идеологию, что значительно усложняет программистам понимание написанного с его помощью кода. В результате, некоторая экономия времени и дискового пространства при разработке, оборачивается убытками при последующей поддержке.
Но, т.к. иногда в ИТ-компаниях Moose все-таки используется, лучше его знать.
Moose работает на основе Class::MOP, который является системой метаклассов для perl 5. Это означает, что можно использовать Moose не только для оптимизации ООП, но и для разработки метаклассов.
Moose подключает Moose::Meta::Class, который является метаклассом, и подклассом Class::MOP::Class. Moose экспортирует несколько функций: extends, with, has, before, after, around, override, augment.
Как создать класс в Moose
Moose позволяет программисту создавать классы, которые могут иметь атрибуты, роли, методы, модификаторы методов, родительсткие классы (суперклассы). Каждый класс имеет собственный конструктор и деструктор.
Вот так выглядит самый простой класс на основе Moose:
|
1 2 3 4 5 6 |
package Moo; use strict; use Moose; 1; |
Создание конструктора осуществляется Moose автоматически, а наличие других методов не являются обязательным условием для создания рабочего класса.
Обычно для создания объекта использовались методы new(). Это было негласным стандартом. Теперь создание экземпляра класса осуществляется внутри Moose, автоматически. Если необходимо, можно определить метод BUILD() в своем классе. Метод будет вызываться Moose сразу после создания объекта.
В стандартном perl, если объект выпадал из области видимости, автоматически вызывалась функция DESTROY(). В Moose для аналогичных задач используется метод DEMOLISH().
Использование new() и DESTROY() в классах на основе Moose — запрещено.
Вызов класса ничем не отличается от вызова стандартного класса perl. Разработчик, использующий ваш класс, вообще не заметит разницы, пока в дело не вступят атрибуты.
|
1 2 3 4 5 |
use strict; use Moo; my $moo_obj = Moo->new(); print $moo_obj."\n"; # выведет Moo=HASH(0xe0ad14) |
Но как бы Moose не пытался выглядеть современным и инновационным, в конечном счете, создание объектов все равно осуществляется старым добрым bless. Пусть и неявно.
Для того, чтобы заставить класс унаследовать что-то от другого класса, можно использовать функцию Moose extends (@superclasses). Данная функция используется вместо привычного use base.
Пример наследования класса
Супер-класс:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
package MooSuper; use strict; use Moose; has 'attr_super' => ( is => 'ro', isa => 'Int', default => 150, ); 1; |
Класс-наследник:
|
1 2 3 4 5 6 7 8 |
package Moo; use strict; use Moose; extends 'MooSuper'; 1; |
Скрипт, использование созданных классов.
|
1 2 3 4 5 6 7 |
use strict; use Moo; my $moo_obj = Moo->new(); print $moo_obj->attr_super; # выведет "150" |
Для организации множественного наследования нужно просто указать список классов через запятую.
Для проверки, является ли переменная объектом, можно использовать функцию blessed.
В самом деле, blessed — это функция Scalar::Util::blessed. Используется вместо ref.
|
1 2 |
(blessed $gd_image && $gd_image->isa('GD::Image')) || die "Bad image ($gd_image), must be an instance of GD::Image"; |
|
1 2 3 4 5 |
use Scalar::Util qw/blessed reftype/; unless (blessed($params{date}) && blessed($params{date}) eq 'Class::Date') { $params{date} = Class::Date::date( $params{date}, $params{timezone} ); } |
Что такое атрибут класса в Moose и как его создать
Атрибут является свойством класса, в котором он определен. Атрибут всегда имеет имя и список определяющих характеристик.
Для создания атрибутов в Moose используется функция has. С ее помощью создается атрибут, задается имя и характеристики для него.
Можно передать has список имен, тогда будет создан атрибут под каждым из имен и каждому будут заданы одни и те же параметры.
Список допустимых характеристик атрибутов:
- is — создает аксессоры для атрибута.
- clearer — создает метод, который очищает значение атрибута, присваивая ему undef.
- predicate — создает метод, который возвращает значение true или false, в зависимости от того, установлено значение атрибуту или нет.
- isa — данная опция задает тип значения атрибута. После указания типа, Moose будет контролировать, чтобы атрибут имел значение только указанного типа.
Возможные значения: Any, Item, Bool, Undef, Defined, Value, Str, Num, Int, ClassName, RoleName, Ref, ScalarRef, ArrayRef, HashRef, CodeRef, RegexpRef, GlobRef, FileHandle, Object. - default — содержит значение атрибута по-умолчанию, которое будет присвоено последнему сразу после его инициализации. Если значение — простой скаляр, то можно прописать его как есть. Если значение — хэш, ссылка и т.п., то придется в качестве обертки использовать sub {}.
- required — флаг задает необходимость установки значения аттрибута. По умолчанию все аттрибуты необязательны.
- trigger — задает код, который вызывается на выполнение сразу после того, как было задано значение атрибута. Триггер срабатывает, когда значение задается в рамках конструктора, либо с помощью аксессора. Установка значения по-умолчанию или builder не вызовут срабатывание триггера.
- builder — содержит название метода, который будет вызываться для получения значения атрибута во время инициализации переменной. Иногда его рекомендуют использовать вместо default.
- lazy — флаг, может иметь значение 1 или 0. Если задан данный флаг, то установка значения атрибута откладывается на самый последний момент. Полезно, когда значение атрибута зависит от множества разных факторов. Более того, значение атрибута будет вычислено только в том случае, если это потребуется в процессе выполнения программы.
Если указано это свойство, атрибуту обязательно должно быть назначено значение по-умолчанию или задан builder.
Пример очень простого атрибута
Создаем атрибут:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
package Moo; use strict; use Moose; has 'attr' => ( is => 'rw', clearer => 'clear_attr', predicate => 'has_attr', ); 1; |
Используем атрибут в программе:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
use strict; use Moo; my $moo_obj = Moo->new(); $moo_obj->attr('value of attr'); # устанавливаем атрибуту значение print $moo_obj->attr; # выведет "value of attr" print $moo_obj->has_attr; # выведет "1" print $moo_obj->clear_attr; # выведет "value of attr" и удалит значение print $moo_obj->attr; # ничего не выведет, т.к. значение атрибута удалено |
По сути, атрибут — это всего лишь переменная, типа флага. Альтернатива использованию глобальных переменных.
Пример атрибута, которому назначено значение по умолчанию и определен тип значения:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package Moo; use strict; use Moose; has 'attr' => ( is => 'ro', isa => 'HashRef', default => sub { { 1011 => { name => "Maria", phone => "1909" }, 1012 => { name => "Jane", phone => "2343" } } }, ); 1; |
Использование атрибута подобного типа:
|
1 2 3 4 5 6 |
use strict; use Moo; my $moo_obj = Moo->new(); print $moo_obj->attr->{1011}->{name}; # выведет "Maria" |
Как создать метод в Moose
Создание методов точно такое же, как в стандартном Perl ООП. Любая функция, описанная в классе, будет считаться методом. Пример создания и использования метода в классе:
|
1 2 3 4 5 6 7 8 9 10 |
package Moo; use strict; use Moose; sub method { return "Any data"; } 1; |
Использование метода в скрипте:
|
1 2 3 4 5 6 7 8 |
use strict; use Moo; my $moo_obj = Moo->new(); my $data = $moo_obj->method; print $data; |
Для переопределения методов родительского класса, Moose предоставляет функцию override ($name, &sub). В самом деле, ее использование совсем не обязательно, и можно переопределить метод как в стандартном Perl, через sub().
Переопределение метода с помощью override
Супер-класс:
|
1 2 3 4 5 6 7 8 9 10 11 |
package MooSuper; use strict; use Moose; sub method { print "Failed"; return 1; } 1; |
Класс-наследник:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Moo; use strict; use Moose; extends 'MooSuper'; override ('method', sub { print "OK"; return 1; } ); 1; |
Скрипт, который использует переопределенный метод:
|
1 2 3 4 5 6 |
use strict; use Moo; my $moo_obj = Moo->new(); $moo_obj->method; |
Что такое модификаторы методов в Moose и как их использовать
Модификаторы методов в Moose сделаны на основе Class::MOP. Все, что вы знаете о модификаторах Class::MOP, пригодится вам для понимания модификаторов Moose.
Модификатор метода позволяет задать некоторый код, который будет вызываться до, после или во время выполнения функции, к которой он привязан. Немного похоже на идею триггеров в БД. Модификаторы очень удобны. Они могут распарсить и подготовить данные для вызываемой функции, могут сделать запрос к БД, чтобы уточнить наличие информации.
Класс, с методом method() и модификаторами для этого метода:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package Moo; use strict; use Moose; sub method { print "It's method()"; return 1; } # модификатор метода. Код модификатора выполняется перед тем, # как выполнится код метода method() before 'method' => sub { my $self = shift; print "Exec before method()"; }; # модификатор метода. Код модификатора выполняется после того, # как выполнится код метода method() after 'method' => sub { my $self = shift; print "Exec after method()"; }; 1; |
Использование класса:
|
1 2 3 4 5 6 |
use strict; use Moo; my $moo_obj = Moo->new(); $moo_obj->method; |
После запуска скрипта на консоль будет выведено:
|
1 2 3 4 |
$ perl Moo.pl Exec before method() It's method() Exec after method() |
Модификатор метода around
Модификатор around принимает в качестве первого аргумента имя метода, к которому его применили. Далее, программист сам, внутри модификатора должен явно вызвать указанный метод. В результате, получается, что модификатор around выполняет роль обертки для метода.
Класс, который содержит модификатор around:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package Moo; use strict; use Moose; sub method { my $self = shift; my $text = shift; print "$text"; } around 'method' => sub { my $orig = shift; my $self = shift; print "<p>"; $self->$orig(@_); print "</p>"; }; 1; |
Использование метода, которому назначен модификатор around:
|
1 2 3 4 5 6 |
use strict; use Moo; my $moo_obj = Moo->new(); $moo_obj->method("Test Filed"); # выведет: "<p>Test Filed</p>" |
Модификатор augment и inner
augment — это один из модификаторов методов. before, after — это код, который будет выполнен до или после выполнения метода.
augment задает код, который будет выполнен внутри метода, в том месте, где вызывается inner(). Т.е. augment — полная противоположность модификатора around.
Модификатор может использоваться только в системе с элементами наследования.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package MooAug; use strict; use Moose; sub method { my $self = shift; print "<p>"; inner(); print "</p>"; } 1; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package Moo; use strict; use Moose; extends 'MooAug'; augment 'method' => sub { my $self = shift; my $text = shift; print "$text"; }; 1; |
|
1 2 3 4 5 6 |
use strict; use Moo; my $moo_obj = Moo->new(); $moo_obj->method("Test Filed"); # выведет: "<p>Test Filed</p>" |
На мой взгляд, это настолько страшный для понимания код, что лучше его не использовать.
Что такое роль в Moose и как ее создать
Роль — это новое понятие в perl, вводимое Moose.
Роль — это группа атрибутов, методов и модификаторов методов, которые дополняют использующий роль класс. Одну и ту же роль могут использовать разные классы.
С помощью роли можно задать список методов, которые обязательно должны быть определены в использующем роль классе. Это делает роль похожей на интерфейс.
Можно задать для роли список несовместимых с ней ролей.
Роли не используют наследование.
После того, как роль встроена в класс, это выглядит так, как будто ее атрибуты и методы были заданы непосредственно в классе.
Пример роли:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package MooRole; use strict; use Moose::Role; has 'attr_role' => ( is => 'rw', isa => 'Int', default => 255, ); sub method_role { my $self = shift; return "Some data"; } 1; |
Для подключения нужной роли используется функция Moose — with (@roles):
|
1 2 3 4 5 6 7 8 |
package Moo; use strict; use Moose; with 'MooRole'; 1; |
Использование класса с подключенными ролями в скрипте:
|
1 2 3 4 5 6 7 |
use strict; use Moo; my $moo_obj = Moo->new(); print $moo_obj->method_role; print $moo_obj->attr_role; |
А теперь второй пример. Попробуем создать роль, которая будет выполнять задачи интерфейса. Т.е. будет только задавать список методов, которые должны быть определены в использующем ее классе.
В стандартном ООП, интерфейс — это класс без полей и без реализации, включающий только заголовки методов. Если некий класс наследует интерфейс, он должен реализовать все входящие в него методы. Интерфейс позволяет быть уверенным, что какой-либо используемый класс реализует все необходимые методы. Данный принцип полезен при разработке продукта большим количеством разработчиков, когда нужно гарантировать совместимость разрабатываемых продуктов.
Официальные конструкции для создания интерфейсов существуют, например, в Java или PHP.
Для задания обязательных методов используется специальная функция requires.
Класс-интерфейс:
|
1 2 3 4 5 6 7 8 |
package MooRole; use strict; use Moose::Role; requires 'method_re'; 1; |
Класс, который использует интерфейс:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
package Moo; use strict; use Moose; with 'MooRole'; sub method_re { return "Any data"; } 1; |
Если в классе, который использует класс-интерфейс, не будет реализована заданная интерфейсом функция, интерпретатор выдаст длинное и выразительное ругательство.
Использование класса:
|
1 2 3 4 5 6 7 |
#!/usr/bin/perl use strict; use Moo; my $moo_obj = Moo->new(); print $moo_obj->method_re; |
Вывод ошибок в Moose с помощью confess
confess — в самом деле является функцией Carp::confess . Экспортируется по историческим причинам.
|
1 |
confess '$rel_info is not defined' unless $attrs->{rel_info}; |
Очистка мусора в Moose
Очистка мусора происходит, если в начале класса подключить прагму namespace::autoclean.
|
1 |
use namespace::autoclean; |
Ускорение работы классов
Если класс в процессе работы не изменяется, то его работу можно ускорить следущим образом:
|
1 |
__PACKAGE__->meta->make_immutable; |
Метаклассы Moose реализованы так, что можно изменить их в любое время. Добавить методы, атрибуты или удалить их. За подобные удобства приходится расплачиваться производительностью программы.
Для того, чтобы вовремя отреагировать на изменение класса, система вынуждена постоянно рекурсивно обходить классы и отслеживать любые изменения в них. make_immutable — это возможность сказать системе, что вы не собираетесь заниматься динамическим расширением метаклассов, и позволяете ей один раз обработать код, закешировать его и использовать в дальнейшем.
Заключение
Надеюсь, данная статья поможет вам лучше понимать Moose и вполне достаточна для того, чтобы начать с ней работу. Чистая Moose — достаточно компактная система. Для расширения ее возможностей существует много дополнительных модулей — MooseX.
В ближайшее время на блоге будет опубликован фото-отчет о поездке программиста на отдых. Подпишитесь на обновления, чтобы заглянуть в закулисье профессии ;)
Полезные ссылки по теме Moose
habr.ru: Moose(X). Продолжение
Че-то не догнал. В двух словах, зачем он нужен этот фреймворк? Сдается мне, все то-же самое можно использовать в стандартном Perl, создавать пакеты, дергать из них данные, методы…
Да, все можно. И Moose совсем не обязателен. Просто некоторые вещи с его помощью делать проще.В основном, интерфейсы — эту возможность я оценила. Реализация интерфейса без Moose вещь более чем творческая и умственно изощренная.
Все остальные возможности Moose не особенно нужны… до тех пор, пока вы не начнете использовать Catalyst. Catalyst — отличный фреймворк, но при этом — весь пропитан Moose. Не зная Moose, использовать Catalyst на профессиональном уровне будет крайне сложно.