Moose Zwang und Bauherren
-
09-10-2019 - |
Frage
Dies ergibt sich aus meiner vorherige Frage über Moose Typen strukturiert. Ich entschuldige mich für die Länge der Frage. Ich wollte sicherstellen, dass ich alle notwendigen Details enthalten.
MyApp::Type::Field
definiert einen strukturierten Typ. Ich benutze Zwang seines value
Attribut zu ermöglichen, leichter von meiner Person
Klasse festgelegt werden (siehe Beispiel unten). Beachten Sie, dass in meiner realen Anwendung, wo der Feldtyp für mehr als nur den Namen einer Person verwendet wird, habe ich auch coerce von einem hashref.
Ich brauche auch die MyApp::Type::Field
size
und required
einstellen schreibgeschützt bei der Erstellung von MyApp::Person
Attribute. Ich kann dies tue eine Builder-Methode verwendet, aber dies wird nicht aufgerufen, wenn Zwang verwendet wird, als mein Zwang direkt ein neues Objekt erstellt, ohne die Builder-Methode.
Ich kann dies umgehen, indem sie eine around
Methodenmodifikator zu MyApp::Person
Zugabe (siehe Beispiel unten), aber das fühlt sich chaotisch. Die around
Methodenmodifikator häufig genannt wird, aber ich nur die Nur-Lese-Attribute einmal einstellen müssen.
Gibt es einen besseren Weg, dies zu tun, während Zwang noch erlaubt? Die MyApp::Type::Field
Klasse kann nicht initialisiert werden size
und required
über Versäumnisse oder Bauherren, da es keine Möglichkeit hat, zu wissen, was die Werte sein sollte.
Es kann nur der Fall sein, dass ich Zwang für, die keinen around
Modifikator verzichten.
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 );
Prints:
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]
einen around
Methodenmodifikator zu MyApp::Person
hinzufügen, und den Builder ändern, so dass es nicht Set size
und 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();
}
Wenn MyApp::Test
ausgeführt wird, size
und required
werden zweimal eingestellt.
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]
Vorgeschlagene Lösung
daotoad des Vorschlag, einen Subtyp für jedes MyApp::Person
Attribut erstellen und Nötigung, dass Subtyps aus einem Str
in eine MyApp::Type::Field
funktioniert ganz Gut. Ich kann sogar mehrere Subtypen, Nötigungen und Attribute erstellen, indem die gesamte Menge in einer for-Schleife gewickelt wird. Dies ist sehr nützlich für mehrere Attribute mit ähnlichen Eigenschaften zu schaffen.
Im Beispiel unten, ich habe eine Delegation mit handles
eingerichtet, so dass $person->get_first_name
zu $person->first_name->value
übersetzt. Hinzufügen Schriftsteller gibt einen gleichwertigen Setter, so dass die Schnittstelle der Klasse recht sauber:
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;
Prints:
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]
Lösung
Wird jeder Mensch unterschiedliche Anforderungen an die name
Feld haben? Dies scheint unwahrscheinlich.
Es scheint wahrscheinlicher, dass Sie eine Reihe von Parametern für jede Field
auf der anderen Anwendung. So definieren eine Art Personname als Subtyp von Field. Ihr Zwang von String Personname wäre. Dann wird der Zwang Code und können die entsprechenden Werte erforderlich und Länge gelten, wenn es Field->new()
nennt.
Auch dies scheint wirklich, wie Sie für ein Objekt Moos ein Attribut-Objekt erstellen, das auf einem Meta-Objekt-System basiert, dass bereits Attribut Objekte zur Verfügung stellt. Warum erstreckt sich nicht Ihre Attributobjekt, anstatt Ihre eigenen machen?
Siehe Elch-Kochbuch Rezepte Meta ??a > weitere Informationen zu diesem Ansatz.