Moose: ООП в Perl

    Moose — расширения для Perl 5, позволяющее упростить реализацию ООП.

    Создание класса происходит очень легко:
    package MyClass;
    use Moose;
    


    все, пустой класс создан. Он может иметь произвольное количество: аттрибутов, методов, суперклассов, модификаторов метода, конструктор(1шт), деструктор(1шт) и мета-класс(1шт) который содержит все метаинформацию о данном классе.

    Теперь подробнее об этих составляющих:


    Класс


    Каждый класс является подклассом Moose::Object. По умолчанию создает кучу всяких полезных объектов, в виде мета-классов и других присущих суперклассу объектов, к которым можно получить доступ после создания.

    Подкласс

    Наследование происходит с помощью ключевого слова extends
    package User; 
    use Moose; 
    
    extends 'Person'; 
    
    has 'username' => ( is => 'rw' ); 
    
    

    множественное наследование, указываем классы через запятую: extends 'Foo', 'Bar';

    Аттрибуты


    Обязательно должно быть имя у аттрибута, может иметь различное количество свойств: флаг read/write, type, accessor method, delegations, значение по умолчанию и др.
    По умолчанию Moose хранит аттрибуты в экземпляре класса, как хэш. Доступ к ним можно получить через аксессоры.
    has — объявляем аттрибут
    has 'first_name' => ( is => 'rw' );
    

    опция is может иметь значения [ro|rw|bare] read-only либо read-write или bare — без аксессоров к данному аттрибуту.

    Геттеры и сеттеры можно установить так:

    has 'weight' => ( 
        is     => 'rw', 
        reader => 'get_weight', 
        writer => 'set_weight', 
    ); 
    


    predicate — если аттрибут «undef» или другое ложное значение, то возвратит истину.
    clearer — сбрасывает аттрибут

    clearer — сбрасывает аттрибут
    has 'ssn' => ( 
        is        => 'rw', 
        clearer   => 'clear_ssn', 
        predicate => 'has_ssn', 
    ); 
    


    $person->has_ssn; # false 
    
    $person->ssn(undef); 
    $person->ssn; # returns undef 
    $person->has_ssn; # true 
    
    $person->clear_ssn; 
    $person->ssn; # returns undef 
    $person->has_ssn; # false 
    
    $person->ssn('123-45-6789'); 
    $person->ssn; # returns '123-45-6789' 
    $person->has_ssn; # true 
    


    Необходимость в установки аттрибута

    можно установить с помощью свойства: required => 1
    по умолчанию все аттрибуты необязательны.

    Аттрибуты можно установить по умолчанию двумя способами.

    has 'size' => (
        is        => 'ro',
        default   => 'medium',
        predicate => 'has_size',
    );
    

    и если не устанавливать в конструкторе, то:
    my $person = Person->new();
    $person->size; # medium
    $person->has_size; # true
    


    А можно установить ссылку на метод:
    has 'size' => (
        is => 'ro',
        default =>
            sub { ( 'small', 'medium', 'large' )[ int( rand 3 ) ] },
                 predicate => 'has_size',
            );
    


    Альтернативный метод:
    has 'size' => (
        is        => 'ro',
        builder   => '_build_size',
        predicate => 'has_size',
    );
    
    sub _build_size {
         return ( 'small', 'medium', 'large' )[ int( rand 3 ) ];
    }
    

    builder рекомендуют использовать вместо default.

    Откладываем установку аттрибута в последнюю очередь

    этого можно достичь установив свойство: lazy => 1
    особенно необходимо, когда начальное значение аттрибута зависит от других факторов.
    Еще один плюс в использовании этого свойства, в том что аттрибут будет вычисляться только в том случае, если потребуется, а если же он вообще не потребуется, то мы сохраним CPU time.

    Типы аттрибутов

    могут быть такой же тип как и любая перл структура: isa => 'Str'
    а могут быть и объектом: does => 'MyApp::Object'

    Установка нескольких аттрибутов

    has [ 'x', 'y' ] => ( is => 'ro', isa => 'Int' );
    

    или так:
    for my $name ( qw( x y ) ) {
        my $builder = '_build_' . $name;
        has $name => ( is => 'ro', isa => 'Int', builder => $builder );
    }
    


    Методы


    Всякая функция, объявленная в классе — это метод класса.

    Модификаторы методов

    вызываются перед(или «before, after, around, augment») вызовом метода:
    before 'atribute' => sub { 
        my $self = shift; 
        my $a   = shift; 
    
        print $a;
    }; 
    


    Роль


    поведение или состояние, которое должен реализовать данный класс. Аналог интерфейса в других ООязыках.
    Создается с помощью use Moose::Role;
    package Eq;
    use Moose::Role;
    
    requires 'equal_to';
    
    sub not_equal_to {
        my ( $self, $other ) = @_;
        not $self->equal_to($other);
    }
    

    хотя роль выглядит как класс, но это не класс, нельзя создать экземпляр роли. Слово requires говорит нам, что любой класс, использующий данную роль должен реализовать метод 'equal_to'.
    package Comparable;
    use Moose;
    with 'Eq';
    
    sub equal_to {
        my ( $self, $other ) = @_;
        $self->compare($other) == 0;
    }
    
    sub compare {
        my ( $self, $other ) = @_;
        $self->amount <=> $other->amount;
    }
    


    Конструктор/Деструктор


    Их никогда не нужно явно переопределять, но если все же возникнета такая необходимость то можно использовать BUILD() и DEMOLISH() для конструктора и деструктора соотвественно.

    Metaclass


    Метакласс описывает класс. С Moose каждый класс дает «meta()», который возвращает Moose::Meta::Class объект. Он говорит о том что представляет из себя данный класс.
    my $meta = User->meta(); 
    
    for my $attribute ( $meta->get_all_attributes ) { 
         print $attribute->name(), "\n"; 
    
         if ( $attribute->has_type_constraint ) { 
             print "  type: ", $attribute->type_constraint->name, "\n"; 
         } 
    } 
    
    for my $method ( $meta->get_all_methods ) { 
         print $method->name, "\n"; 
    } 
    


    Очистка мусора


    происходит если установить в начале класса: use namespace::autoclean;
    или в конце класса: no Moose;

    Ускорение работы классов


    Если класс в процессе работы не изменяется, то его работу можно ускорить следущим образом:
    __PACKAGE__->meta->make_immutable;
    


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

    Похожие публикации

    Комментарии 23
      +9
      Маленькое дополнение:
      >Moose — расширения для Perl 5, позволяющее упростить реализацию ООП.

      Правильней было бы
      «Moose — расширения для Perl 5, позволяющее усложнить реализацию ООП» — так как самый простой способ использовать ООП в перле — ничего кроме штатных средств не использовать.

      А ещё правильней — Moose позволяет превратить Perl в другой язык (даже в несколько разных языков), с неожиданными свойствами, и насколько это оправдано — очень дискуссионный вопрос, есть доводы и за и против.
        0
        действительно, я когда изучал классы на перле, то bless довольно долго понимал, а moose — судя по реализации еще более замудренный и для его освоения все-таки нужно знать bless.
        по всей видимости moose — это зачаток фреймворка какого-то
          +2
          1. Moose в разы проще и изящнее чем старый способ ООП
          2. Понимание работы ООП каким оно было до Moose не обязательно для работы с ним
            +2
            1. Спорный момент. Да, в других языках такого нет (ну или по крайней мере я не встречал), но _проще_ ли оно от того? bless — это простое присвоение хешу (или иной сущности) метки — это «объект такой-то и оператор стрелка должна вести себя соответствующим образом ( $obj->f(1,2,3) === Obj::f($obj, 1, 2, 3) )». Во всем остальном работает со старым добрым Perl. Например, объект можно сериализировать обычным encode_json. Куда уж проще и изящнее?
              0
              Да, дорогой, он реально проще.
              Сравним реализацию класса Person с конструктором, одним атрибутом, сеттером и геттером, ограничением по типу и обработкой ошибок.

              Moose

              package Person;
              use Moose;
              
              has last_name => (
                  is  => 'rw',
                  isa => 'Str',
              );
              


              Pure perl

              package Person;
              use strict;
              use warnings;
              use Carp 'confess';
              
              sub new {
                  my $class = shift;
                  my %args  = @_;
                  my $self  = {};
              
                  if (exists $args{last_name}) {
                      confess "Attribute (last_name) does not pass the type constraint because: "
                              . "Validation failed for 'Str' with value $args{last_name}"
                          if ref($args{last_name});
                      $self->{last_nane} = $args{last_name};
                  }
              
                  return bless $self, $class;
              }
              
              sub last_name {
                  my $self = shift;
              
                  if (@_) {
                      my $value = shift;
                      confess "Attribute (last_name) does not pass the type constraint because: "
                              . "Validation failed for 'Str' with value $value"
                          if ref($value);
                      $self->{last_name} = $value;
                  }
              
                  return $self->{last_name};
              }
              


              И представим, что в сеттере мы опечатались и написали, допустим, $self->{last_mame}…

              Я люблю perl, но ооп в нём изначально не было, пришлось костылять.
                0
                Ну я вас умоляю. Вы описали только один атрибут. Но ни один нормальный человек не будет описывать в таком стиле 10 атрибутов, он их сгенерит любым удобным для него образом и в целом получит кусок того же самого Mo[ou]se, только без XS-реализации аксессоров, без ненужных в данном конкретном случае наворотов, и с большой вероятностью более компактный по интерфейсу и стилю описания. Да, у сабжа есть одно достоинство — он уже написан и уже умеет многое, и второе достоинство — он (по непонятным лично мне причинам) уже является одним из стандартов разработки. С другой стороны, вот вы выше написали пример аксессора, который (если сделать текст в confess короче) занимает менее 10 строчек, включая закрывающие фигурные скобки. Добавить сюда lazy — это завернуть строчки в еще один простой блок. Добавить билдеры — тоже тривиальное действие, несколько вариантов реализации на любой вкус. Начните писать новый проект — и грошовый по сложности функционал аксессоров довольно быстро и естественным образом осядет у вас в суперклассе, будет понятен и прост для восприятия и не будет иметь никаких космических возможностей, которые пока не нужны. Да и вообще, если речь только про аксессоры — на cpan хватает более простых реализаций. Из всего функционала, описанного в статье, пожалуй только augment не выглядит простой синтаксической надстройкой над тем, что уже есть в перле.
                Если честно, я очень хочу поверить в Moose. Он у нас успешно используется, я уже писал с его помощью несложные классы, и разбирал чужие, но как-то без особой радости. Никак не могу понять, где же та граница, когда его использование становится действительно оправданным.
            0
            Это вы просто Moose не поняли ещё.
            Это другой способ объявлять классы, с кучей удобных возможностей прямо «из коробки».
            Чистый ооп в perl понять труднее, а Moose позволяет отвлечься от реализации ооп в perl и сразу делать классы и объекты.
            В каком-то смысле это фреймворк, но это ведь не ругательное слово.
          +1
          За заметку спасибо, НО

          «этих знаний должно быть достаточно, чтобы успешно создавать ООП структуры на языке Perl»

          Достаточно уметь пользоваться bless. Moose имеет право на жизнь, но bless быстрее и не требует сторонних модулей. И если уж очень хочется синтаксиса, отдаленно напоминающего традиционный, уж лучше использовать Mouse. Насколько я помню, он всего лишь на 40% медленнее blessed хешей.

          Ну и, пожалуй, рискну сослаться на свою заметку по теме: eax.me/perl5-oop/
            +2
            Лопаты тоже достаточно, чтобы выкопать котлован, но экскаватор лучше.
            Если писать всё на «чистом» perl, код получается в разы длиннее, соответственно в разы больше вероятность опечататься.
            Я сначала сомневался, производительность или удобство. Потом подумал, что проще сервак взять побыстрее, чем мучаться и выискивать, где там опечатка в геттерах и сеттерах… Всё-таки perl изначально не был рассчитан на ООП, отсюда и неудобство.
            Я сам и правда остановился на Mouse, как компромиссе. MooseX модули не использую, и бог с ними.
              0
              С трудом верю. Пока что, по моим наблюдениям, код на Mouse/Moose получается заметно длиннее, чем без оного. Как Вам удалось сэкономить строчки, да еще в разы?
          +2
          Стоило бы еще упомянуть, какой огромный оверхед при использовании Moose, я когда писал на Perl последний раз использовал Moo — полегче, побыстрее, понятнее search.cpan.org/dist/Moo/lib/Moo.pm
            +1
            А насколько огромный оверхед? В документации на Moose, если не ошибаюсь, только про оберточные функции написано в контексте производительности.

              +2
              Никакого «огромного» оверхеда там нет. Если, конечно, писать грамотно.
              Не надо абузить проверки типов и модификаторы методов, делайте все классы неизменяемыми (immutable), и будет вам счастье.
              +1
              А как Moose будет себя вести с отладчиками (с тем же ptkdb)? В случае с bless'ом всё просто, я могу видеть все атрибуты разом и следить за их изменениями.
                +3
                Точно так же.
                Выше писали, что Moose превращает Perl в другой язык. Я считаю это неправильным и сбивающим с толку. Язык старый, базовые принципы — старые. Moose выдает самые обычные блесснутые хеши в виде объектов, просто у них есть несколько «магических» методов.
                  0
                  А Вы пробовали проходить отладчиком по Moose коду? Похоже нет.
                  Насчёт нового языка — если под Moose понимать только то, что описано в статье, конечно это не другой язык. Только в таком использовании Moose смысла нет — по сравнению с bless почти нет выигрыша, вся прелесть начинается при использовании дополнительных модулей, при построении своей системы типов, использовании валидации.
                    0
                    А где можно почитать подробнее о реальных прелестях Moose? Беглый осмотр показал только какие-то негативные отзывы в духе use Perl^WMoose^WMozg or die;… и то, что он используется в Catalyst и это круто.
                      0
                      Ну, если мануал почитать…
                      Для начала, concepts, classes, attributes.
                      search.cpan.org/~doy/Moose-2.0602/lib/Moose/Manual.pod

                      Вот, презентация по-быстрому:
                      www.houseabsolute.com/presentations/intro-moose-class/index.html

                      Потом, есть ещё книжка modern perl, легко нагуглить. Там в главе «объекты» просто написано — система в perl очень гибкая, но в ежедневных задачах помощи от неё маловато, поэтому modern perl oop == Moose. И дальше про Moose.
                      +1
                      Стал бы я писать, если бы не проходил. Единственное, что реально достает — «джава–стайл» стектрейсы на два экрана. Остальное вполне терпимо, потому что отладчиком надо проходить не по Moose, а по своему приложению. Moose пусть отлаживают его разработчики.
                        0
                        Кстати, я не пытаюсь оправдать использование Moose там, где оно не нужно. Просто говорить, что он превращает Perl во что–то другое — неправильно. Из–за этого люди потом его «боятся».
                        Если вам надо всего–то генератор аксессоров и конструкторов, то да, есть методы попроще.
                  +1
                  Господа, не забывайте про всем известный принцип TIMTOWTDI, особенно актуальный для такого языка как Perl
                    0
                    Не забываем, но как раз для перла это очень актуально — кто-то потом должен будет поддерживать все эти пути.

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

                  Самое читаемое