2010-12-13 62 views
5

這跟從我的previous question關於穆斯結構類型。我對問題的長度表示歉意。我想確保包含所有必要的細節。麋強制和建設者

MyApp::Type::Field定義了一個結構化類型。我使用強制來允許從我的Person類更容易地設置其value屬性(請參見下面的示例)。請注意,在我的真實應用程序中,Field類型不僅用於一個人的名字,而且還來自HashRef。

我還需要在構建時設置MyApp::Type::FieldsizerequiredMyApp::Person的只讀屬性。我可以使用builder方法執行此操作,但如果使用強制,則不會調用此方法,因爲我的強制直接創建了一個新對象,而不使用構建器方法。

我可以通過將around方法修飾符添加到MyApp::Person(請參見下面的示例)來解決此問題,但這感覺很混亂。方法修飾符around經常被調用,但我只需要設置只讀屬性一次。

有沒有更好的方法來做到這一點,同時仍然允許強制? MyApp::Type::Field類無法通過默認值或構建器初始化sizerequired,因爲它無法知道值應該是什麼。

可能僅僅是因爲我放棄了強制而不支持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); 

打印:

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] 

添加around方法改性劑MyApp::Person,並更改生成器,它不設置sizerequired

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(); 
} 

MyApp::Test運行,sizerequired是設置兩次。

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] 

建議的解決方案

​​建議創建每個MyApp::Person屬性的亞型,並強迫從Str該亞型爲MyApp::Type::Field作品相當不錯的。我甚至可以通過在for循環中包裝整個批次來創建多個子類型,強制和屬性。這對於創建具有類似屬性的多個屬性非常有用。

在下面的示例中,我使用handles設置了委派,以便$person->get_first_name被翻譯爲$person->first_name->value。添加一個作家給人提供了一個相當的二傳手,使得接口的類挺乾淨:

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; 

打印:

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] 

回答

3

是每個人將不得不爲name領域有不同的要求?這似乎不大可能。

看起來你更有可能在應用程序中爲每個Field設置一組參數。因此,將一個PersonName類型定義爲Field的子類型。您的強制將從字符串到PersonName。然後強制代碼,並且可以在調用Field->new()時將適當的值應用到所需的長度。

另外,這看起來好像是在爲一個Moose對象構建一個屬性對象,該對象基於一個已經提供屬性對象的元對象系統。爲什麼不擴展你的屬性對象而不是自己做?

有關此方法的更多信息,請參閱Moose Cookbook Meta Recipes

+1

該字段更像MooseX :: Types ::與具有元屬性的屬性相比的結構。使用的一個例子是每個字段都需要一個值,最大長度(大小)和所需標誌的Web表單。該模型(在本例中爲Person類)設置了大小和所需的標誌。因此「Field」是相當通用的,而「Person」類更具體。例如,我查看過元屬性,但訪問(例如,'$ person-> meta-> get_attribute('name') - > size()')有點不方便。子類型可能是一個選項。我會研究這個...... – Mike 2010-12-13 17:41:46

+0

我剛剛嘗試創建子類型,並認爲它可能會提供一個很好的解決方案。我明天會做更多的測試......謝謝。 – Mike 2010-12-13 23:18:22

+0

我已經使用建議的解決方案更新了我的答案,該解決方案使用了您的子類型建議。謝謝你的建議。 – Mike 2010-12-14 09:35:53