2010-12-13 13 views
5

Wynika to z moich o typach strukturalnych Moose. Przepraszam za długość pytania. Chciałem upewnić się, że zawarłem wszystkie niezbędne szczegóły.Wymuszenie łosia i budowniczowie

MyApp::Type::Field definiuje typ strukturalny. Używam przymusu, aby umożliwić łatwiejsze ustawienie jego atrybutu value z mojej klasy Person (patrz przykład poniżej). Zauważ, że w mojej prawdziwej aplikacji, gdzie typ pola jest używany dla czegoś więcej niż tylko nazwisko osoby, ja również przymuszam z HashRef.

Muszę również ustawić atrybuty tylko do odczytu MyApp::Type::Field od MyApp::Person w czasie kompilacji. Mogę to zrobić za pomocą metody budowania, ale nie jest to wywoływane, jeśli stosuje się przymus, ponieważ mój przymus tworzy bezpośrednio nowy obiekt, bez użycia metody konstruktora.

Mogę to obejść, dodając modyfikator metody around do MyApp::Person (patrz przykład poniżej), ale to jest nieprzyjemne. Modyfikator metody around jest często wywoływany, ale muszę tylko raz ustawić atrybuty tylko do odczytu.

Czy istnieje lepszy sposób, aby to zrobić, nadal dopuszczając przymus? Klasa MyApp::Type::Field nie może zainicjować wartości size i required za pomocą domyślnych ustawień lub budowania, ponieważ nie ma możliwości sprawdzenia, jakie wartości powinny być.

Może po prostu być tak, że rezygnuję z przymusu na rzecz braku modyfikatora around.

MyApp::Type::Field

coerce 'MyApp::Type::Field' 
    => from 'Str' 
     => via { MyApp::Type::Field->new(value => $_) }; 

has 'value' => (is => 'rw'); 
has 'size'  => (is => 'ro', isa => 'Int', writer => '_set_size',  predicate => 'has_size'); 
has 'required' => (is => 'ro', isa => 'Bool', writer => '_set_required', predicate => 'has_required'); 

MyApp::Person

has name => (is => 'rw', isa => 'MyApp::Type::Field', lazy => 1, builder => '_build_name', coerce => 1);  

sub _build_name { 
    print "Building name\n"; 
    return MyApp::Type::Field->new(size => 255, required => 1); 
} 

MyApp::Test

print "Create new person with coercion\n"; 
my $person = MyApp::Person->new(); 
print "Set name\n"; 
$person->name('Joe Bloggs'); 
print "Name set\n"; 
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required); 

print "Create new person without coercion\n"; 
$person = MyApp::Person->new(); 
print "Set name\n"; 
$person->name->value('Joe Bloggs'); 
print "Name set\n"; 
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required); 

Wydruki:

Create new person with coercion 
Set name 
Name set 
Name: Joe Bloggs [0][0] 

Create new person without coercion 
Set name 
Building name 
Name set 
Name: Joe Bloggs [255][2] 

Dodaj modyfikator around metody do MyApp::Person i zmień builder, tak że nie ustawia size i required:

around 'name' => sub { 
    my $orig = shift; 
    my $self = shift; 

    print "Around name\n"; 

    unless ($self->$orig->has_size) { 
     print "Setting size\n"; 
     $self->$orig->_set_size(255); 
    }; 

    unless ($self->$orig->has_required) { 
     print "Setting required\n"; 
     $self->$orig->_set_required(1); 
    }; 

    $self->$orig(@_); 
}; 

sub _build_name { 
    print "Building name\n"; 
    return MyApp::Type::Field->new(); 
} 

Kiedy MyApp::Test uruchomieniu size i required są ustawić dwukrotnie.

Create new person with coercion 
Set name 
Around name 
Building name 
Setting size 
Setting required 
Name set 
Around name 
Setting size 
Setting required 
Around name 
Around name 
Name: Joe Bloggs [255][3] 

Create new person without coercion 
Set name 
Around name 
Building name 
Name set 
Around name 
Around name 
Around name 
Name: Joe Bloggs [255][4] 

Proponowane rozwiązanie

daotoad's sugestii tworzenia podtypu dla każdego atrybutu MyApp::Person oraz zmuszanie tego podtypu od A Str do MyApp::Type::Field dosyć skutecznie. Mogę nawet tworzyć wiele podtypów, koercji i atrybutów przez owijanie całej partii w pętlę for. Jest to bardzo przydatne do tworzenia wielu atrybutów o podobnych właściwościach.

W poniższym przykładzie ustawiłem delegowanie przy użyciu handles, aby $person->get_first_name zostało przetłumaczone na $person->first_name->value. Dodawanie pisarzem daje zapewnia równoważną setter, dzięki czemu interfejs do klasy dość czyste:

package MyApp::Type::Field; 

use Moose; 

has 'value'  => (
    is   => 'rw', 
); 

has 'size'  => (
    is   => 'ro', 
    isa   => 'Int', 
    writer  => '_set_size', 
); 

has 'required' => (
    is   => 'ro', 
    isa   => 'Bool', 
    writer  => '_set_required', 
); 

__PACKAGE__->meta->make_immutable; 
1; 

package MyApp::Person; 
use Moose; 
use Moose::Util::TypeConstraints; 
use namespace::autoclean; 

{ 
    my $attrs = { 
     title  => { size => 5, required => 0 }, 
     first_name => { size => 45, required => 1 }, 
     last_name => { size => 45, required => 1 }, 
    }; 

    foreach my $attr (keys %{$attrs}) { 

     my $subtype = 'MyApp::Person::' . ucfirst $attr; 

     subtype $subtype => as 'MyApp::Type::Field'; 

     coerce $subtype 
      => from 'Str' 
       => via { MyApp::Type::Field->new(
        value => $_, 
        size  => $attrs->{$attr}{'size'}, 
        required => $attrs->{$attr}{'required'}, 
       ) }; 

     has $attr => (
      is  => 'rw', 
      isa  => $subtype, 
      coerce => 1, 
      writer => "set_$attr", 
      handles => { "get_$attr" => 'value' }, 
      default => sub { 
       MyApp::Type::Field->new(
        size  => $attrs->{$attr}{'size'}, 
        required => $attrs->{$attr}{'required'}, 
       ) 
      }, 
     ); 
    } 
} 

__PACKAGE__->meta->make_immutable; 
1; 

package MyApp::Test; 

sub print_person { 
    my $person = shift; 

    printf "Title:  %s [%d][%d]\n" . 
      "First name: %s [%d][%d]\n" . 
      "Last name: %s [%d][%d]\n", 
      $person->title->value || '[undef]', 
      $person->title->size, 
      $person->title->required, 
      $person->get_first_name || '[undef]', 
      $person->first_name->size, 
      $person->first_name->required, 
      $person->get_last_name || '[undef]', 
      $person->last_name->size, 
      $person->last_name->required; 
} 

my $person; 

$person = MyApp::Person->new(
    title  => 'Mr', 
    first_name => 'Joe', 
    last_name => 'Bloggs', 
); 

print_person($person); 

$person = MyApp::Person->new(); 
$person->set_first_name('Joe'); 
$person->set_last_name('Bloggs'); 

print_person($person); 

1; 

Drukuje:

Title:  Mr [5][0] 
First name: Joe [45][6] 
Last name: Bloggs [45][7] 
Title:  [undef] [5][0] 
First name: Joe [45][8] 
Last name: Bloggs [45][9] 

Odpowiedz

3

Czy każda osoba będzie mieć różne wymagania dotyczące pola name? Wydaje się to mało prawdopodobne.

Wydaje się bardziej prawdopodobne, że masz zestaw parametrów dla każdego Field w całej aplikacji. Więc zdefiniuj typ PersonName jako podtyp Field. Twój przymus będzie pochodził z ciągu znaków na PersonName. Następnie kod przymusu i można zastosować odpowiednie wartości do wymaganych i długości, gdy wywoła Field->new().

Wygląda na to, że budujesz obiekt atrybutu dla obiektu Łoś, który jest oparty na systemie metaobiektów, który już zapewnia obiekty atrybutów. Dlaczego nie rozszerzyć swojego obiektu atrybutów, zamiast tworzyć własne?

Aby uzyskać więcej informacji na temat tego podejścia, zobacz stronę Moose Cookbook Meta Recipes.

+1

Pole jest bardziej jak MooseX :: Typ :: Structured niż atrybut z atrybutami meta. Jednym z przykładów użycia jest formularz internetowy, w którym każde pole wymaga wartości, maksymalnej długości (rozmiaru) i wymaganej flagi. Model (klasa Person w tym przykładzie) ustawia rozmiar i wymaganą flagę. "Pole" ma zatem być dość ogólne, podczas gdy klasa "Osoba" jest bardziej szczegółowa. Przedtem patrzyłem na meta-atrybuty, ale są one nieco niewygodne w dostępie (np. '$ Person-> meta-> get_attribute ('name') -> size()'). Podtyp może być opcją. Przyjrzę się temu ... – Mike

+0

Właśnie eksperymentowałem z tworzeniem podtypu i uważam, że może to być dobre rozwiązanie. Zrobię więcej testów jutro ... Dzięki. – Mike

+0

Zaktualizowałem swoją odpowiedź proponowanym rozwiązaniem, które wykorzystuje sugestię podtypu. Dzięki za radę. – Mike

Powiązane problemy