2015-06-26 6 views
4

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

Я адаптировал дизайн к более универсальному. При следующей базовой настройки:

package First; 
use Moose; 
use Second::Type1; 
use Second::Type2; 
has 'type1' => (
    is => 'rw', 
    isa => 'Second::Type1', 
    default => sub {Second::Type1->new(name => 'random')} 
); 

has 'type2' => (
    is => 'rw', 
    isa => 'Second::Type2', 
    default => sub {Second::Type2->new(name => 'random')} 
); 

package Second::Type1; 
use Moose; 
use This; 
has 'name' => (
    is => 'rw', 
    isa => 'Str', 
    required => 1, 
); 
has 'this' => (
    is => 'rw', 
    isa => 'This', 
    default => sub {This->new()} 
); 
# package has more attributes, but you get the idea 
__PACKAGE__->meta->make_immutable(); 
no Moose; 
1; 

package Second::Type2; 
use Moose; 
use That; 
has 'name' => (
    is => 'rw', 
    isa => 'Str', 
    required => 1, 
); 
has 'that' => (
    is => 'rw', 
    isa => 'That', 
    default => sub {That->new()} 
); 
# package has more attributes, but you get the idea 
__PACKAGE__->meta->make_immutable(); 
no Moose; 
1; 

Я хочу, чтобы иметь возможность построить первый, говоря:

use First; 
my $first = First->new(type1 => 'foo', type2 => 'bar'); 

где «Foo» равно значению для второго :: атрибут «имя» Type1 в и ' bar 'равно значению для атрибута «name» второго :: Type2.

Теперь, что касается моего собственного решения, я (успешно) сделал Moose :: Role, который содержит только «вокруг BUILDARGS», а затем использует класс Factory (содержимое которого здесь не имеет значения IMO) :

package Role::SingleBuildargs; 

use Moose::Role; 
use Factory::Second; 
requires 'get_supported_args'; 

around BUILDARGS => sub { 
my ($class, $self, %args) = @_; 
my @supported_args = $self->get_supported_args; 
my $factory = Factory::Second->new(); 
    my @errors =(); 
    foreach my $arg (sort {$a cmp $b} keys %args) { 
     if (grep {$_ eq $arg} @supported_args) { 
      my $val = $args{$arg}; 
      if (!ref $val) { # passed scalar init_arg 
       print "$self (BUILDARGS): passed scalar\n"; 
       print "Building a Second with type '$arg' and name '$val'\n"; 
       $args{$arg} = $factory->create(type => $arg, name => $val) 
      } elsif (ref $val eq 'HASH') { # passed hashref init_arg 
       print "$self (BUILDARGS): passed hashref:\n"; 
       my %init_args = %$val; 
       delete $init_args{name} unless $init_args{name}; 
       $init_args{type} = $arg; 
       $args{$arg} = $factory->create(%init_args); 
      } else { # passed another ref entirely 
       print "$self (BUILDARGS): cannot handle reference of type: ", ref $val, "\n"; 
       die; 
      } 
     } else { 
      push @errors, "$self - Unsupported attribute: '$arg'"; 
     } 
    } 
    if (@errors) { 
     print join("\n", @errors), "\n"; 
     die; 
    } 
    return $self->$class(%args); 
    }; 

no Moose; 
1; 

, а затем я использую эту роль в первом классе и других классах, таких как First.

Я также попытался принуждении через:

package Role::Second::TypeConstraints; 
use Moose::Util::TypeConstraints 

subtype 'SecondType1', as 'Second::Type1'; 
subtype 'SecondType2', as 'Second::Type2'; 
coerce 'SecondType1', from 'Str', via {Second::Type1->new(name => $_}; 
coerce 'SecondType2', from 'Str', via {Second::Type2->new(name => $_}; 

no Moose::Util::TypeConstraints; 
1; 

и модифицировали первый пакет (листинг только изменения):

use Role::Second::TypeConstraints; 
has 'type1' => (
    isa => 'SecondType1', 
    coerce => 1, 
); 
has 'type2' => (
    isa => 'SecondType2', 
    coerce => 1, 
);  

Это, однако, не работает. Если бы кто-то мог объяснить, почему это было бы здорово.

Что касается фактического вопроса: что является лучшим способом получить такое поведение в ваших классах? Нет ли лучшего способа, чем изменение BUILDARGS, или я пропустил что-то (о Moose :: Util :: TypeConstraints, возможно)? TMTOWTDI и все, но мои не кажутся эффективными вообще.

EDIT: отредактированы для последовательности (вперемешку родовые имена классов)

+0

Я делал такие вещи в прошлом, используя принуждение. Не могу понять, почему ваша существующая установка принуждения не делает этого. Вы вспомнили, что обычно является основным камнем преткновения, который добавляет метасимволу принуждения к атрибутам, в которых вы хотите добиться принуждения. Я предлагаю настойчиво с принуждением. – zgpmax

+0

Это именно то, для чего «BUILDARGS». – simbabque

ответ

2

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

  • Добавить

    use Moose::Util::TypeConstraints; 
    

    к First, Second::Type1 и Second::Type2

  • Добавить действие Принуждение к Second::Type1

    coerce 'Second::Type1' 
        => from 'Str' 
         => via { Second::Type1->new(name => $_) }; 
    

    и Second::Type2

    coerce 'Second::Type2' 
        => from 'Str' 
         => via { Second::Type2->new(name => $_) }; 
    
  • Включить принуждением для type1 и type2 атрибутов First

    has 'type1' => (
        is  => 'rw', 
        isa  => 'Second::Type1', 
        default => sub { Second::Type1->new }, 
        coerce => 1, 
    ); 
    
    has 'type2' => (
        is  => 'rw', 
        isa  => 'Second::Type2', 
        default => sub { Second::Type2->new }, 
        coerce => 1, 
    ); 
    

Тогда вы можете создать First объект точно так, как вы говорите, с

my $first = First->new(type1 => 'foo', type2 => 'bar'); 
+0

Ваше решение кажется более элегантным, чем определение пользовательского типа только ради одного класса, как и я. Я сделаю это. – etripe

+0

Пробовал это сейчас. Работает, как вы сказали. Для большей ясности: «использовать Moose :: Util :: TypeConstraints;» также необходимо добавить в пакет «Первый» для любого принуждения, чтобы работать над его атрибутами, но я уверен, что это подразумевалось в вашем посте, Бородин. – etripe

+0

@etripe: Я рад, что у вас есть решение. Я бы забыл эту модификацию, спасибо. Я добавил его на свой пост. Кстати, я не думаю, что элементы 'default' верны, так как конструктору нужен атрибут' name' для работы – Borodin

1

Вы, безусловно, можете сделать это с типами и принуждения. Попробуйте использовать MooseX::Types вместо встроенных.

Вот небольшой пример, частично снятый с документов MooseX :: Types.

package Foo; 
use Moose; 
use MooseX::Types -declare => [qw(MyDateTime)]; 
use MooseX::Types::Moose 'Int'; 
use DateTime; 

class_type MyDateTime, { class => 'DateTime' }; 
coerce MyDateTime, from Int, via { DateTime->from_epoch(epoch => $_) }; 

has date => (
    is  => 'ro', 
    isa  => MyDateTime, 
    default => sub {time}, 
    coerce => 1, 
); 

package main; 

my $foo = Foo->new; 
say 'without args: ', $foo->date; 

my $bar = Foo->new(date => time - 24 * 3600); 
say 'with args: ', $bar->date; 

Это будет печатать что-то вроде:

without args: 2015-06-26T13:35:38 
with args: 2015-06-25T13:35:38 

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

+0

Спасибо за предложение. Я посмотрю, что ты сказал. Я также посмотрю документацию, чтобы узнать, что я получу, используя MooseX :: Types поверх обычного Moose :: Util :: TypeConstraints. – etripe