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]
War es hilfreich?

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 weitere Informationen zu diesem Ansatz.

scroll top