2015-06-26 3 views
4

С Moose::Manual::BestPractices страницы:Почему «очень плохая практика» отменяет новые при использовании Moose?

Переопределение new является очень плохая практика. Вместо этого вы должны использовать методы BUILD или BUILDARGS, чтобы сделать то же самое. Когда вы переопределяете new, Moose больше не может встроить конструктор, когда ваш класс является универсальным.

Мой вопрос: почему это считается очень плохой практикой?

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


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

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

# Some pseudo code showing the logic of what I attempted 
around BUILDARGS => sub { 
    my ($orig, $self, %args) = @_; 

    # loop through objects in list 
    for my $object (@list) { 

     # if $args used to define the new object 
     # match the arguments of any object in the list 
     # return the matched object 

     # I want this to directly return the object 
     # and skip the call to BUILD 
    } 

    return $self->orig(
     # Insert args here 
    ); 
}; 

sub BUILD { 
    my ($self) = @_; 

    # I don't want this call to happen if the object already existed 
    push @list, $self; 
} 

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

Единственный способ, которым мне удалось обойти это, было переопределить new и вернуть его существующий объект без создания нового.

# Some pseudo code showing the overridden constructor 
sub new { 
    my ($class, %args) = @_; 

    # loop through objects in list 
    for my $object (@list) { 

     # if $args used to define the new object 
     # match the arguments of any object in the list 
     # return the matched object 
    } 

    # Build the object 
    my $self = bless { 
     # Insert args here 
    }, $class; 

    # Add the object to the list 
    push @list, $object; 
} 

Переопределение new работал, но если это на самом деле такая ужасная идея, так как документация Moose, кажется, предполагает, есть лучший способ сделать это?

ответ

1

Я нашел еще одну проблему с переопределением new, кроме Moose, не являющейся в состоянии in-line конструктор. Я обнаружил, что если вы переопределяете new, это предотвратит возможность использования Moose атрибутов «требуется» при вызове конструктора.


Например, если переопределить new и определить класс с атрибутом, как так:

has 'var' => (is => 'rw', isa => 'Str', => required => 1);

Moose позволит вам создать новый экземпляр этого класса, не передавая его значение для var.


Я нашел работоспособное решение для того, что я хочу сделать, по крайней мере, я считаю, что он работоспособен. Мне не нужна скорость встроенный конструктор (я не создаю миллионы объектов), но мне нужна возможность «требовать» атрибутов. Если вы используете функцию around, предоставленную Moose, вы можете существенно переопределить новую, не предотвращая Moose от возможности «требовать» атрибутов.

например:

around new => sub { 
    my ($orig, $class, %args) = @_; 

    # loop through objects in list 
    for my $object (@list) { 

     # if $args used to define the new object 
     # match the arguments of any object in the list 
     # return the matched object without calling new 
    } 

    # Create a new object 
    my $self = $class->orig(%args); 

    # Add it to the list of objects. 
    push @list, $self; 
}; 

Этот подход все еще дает предупреждение о невозможности создать в линии конструктор, но по крайней мере он будет функционировать правильно.

Примечание: добавление __PACKAGE__->meta->make_immutable('inline_constructor' => 0); в конец вашего пакета подавляет это предупреждение.

1

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

+0

Я объявляю пакет неизменным, но мне нужно было добавить 'inline_constructor => 0'. Есть ли какой-либо способ достичь желаемой функциональности без полного переопределения «новых»? Или, если это невозможно, существует ли способ обеспечить, чтобы переопределенный «новый» все еще был «встроенным»? – tjwrona1992

+1

@ tjwrona1992: Предлагаю вам взглянуть на ['MooseX :: Role :: Flyweight'] (https://metacpan.org/pod/MooseX::Role::Flyweight), который добавляет метод' instance', который ведет себя то же, что и 'new', но возвратит ранее созданный объект, если он был вызван ранее с теми же параметрами. – Borodin

+0

Это похоже на потенциальное решение; однако он не является частью стандартного распределения ActivePerl. Где я работаю, сетевая безопасность очень строгая, и мы не можем ничего загрузить, поэтому я не могу получить какие-либо модули из cpan. Даже с помощью команды cpan для установки модулей происходит сбой. Есть ли способ сделать это без не-основного модуля? – tjwrona1992