這跟從我的previous question關於穆斯結構類型。我對問題的長度表示歉意。我想確保包含所有必要的細節。麋強制和建設者
MyApp::Type::Field
定義了一個結構化類型。我使用強制來允許從我的Person
類更容易地設置其value
屬性(請參見下面的示例)。請注意,在我的真實應用程序中,Field類型不僅用於一個人的名字,而且還來自HashRef。
我還需要在構建時設置MyApp::Type::Field
size
和required
MyApp::Person
的只讀屬性。我可以使用builder方法執行此操作,但如果使用強制,則不會調用此方法,因爲我的強制直接創建了一個新對象,而不使用構建器方法。
我可以通過將around
方法修飾符添加到MyApp::Person
(請參見下面的示例)來解決此問題,但這感覺很混亂。方法修飾符around
經常被調用,但我只需要設置只讀屬性一次。
有沒有更好的方法來做到這一點,同時仍然允許強制? MyApp::Type::Field
類無法通過默認值或構建器初始化size
和required
,因爲它無法知道值應該是什麼。
可能僅僅是因爲我放棄了強制而不支持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
,並更改生成器,它不設置size
和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();
}
當MyApp::Test
運行,size
和required
是設置兩次。
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]
該字段更像MooseX :: Types ::與具有元屬性的屬性相比的結構。使用的一個例子是每個字段都需要一個值,最大長度(大小)和所需標誌的Web表單。該模型(在本例中爲Person類)設置了大小和所需的標誌。因此「Field」是相當通用的,而「Person」類更具體。例如,我查看過元屬性,但訪問(例如,'$ person-> meta-> get_attribute('name') - > size()')有點不方便。子類型可能是一個選項。我會研究這個...... – Mike 2010-12-13 17:41:46
我剛剛嘗試創建子類型,並認爲它可能會提供一個很好的解決方案。我明天會做更多的測試......謝謝。 – Mike 2010-12-13 23:18:22
我已經使用建議的解決方案更新了我的答案,該解決方案使用了您的子類型建議。謝謝你的建議。 – Mike 2010-12-14 09:35:53