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]
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
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
Zaktualizowałem swoją odpowiedź proponowanym rozwiązaniem, które wykorzystuje sugestię podtypu. Dzięki za radę. – Mike