使用 Moose 程式設計/語法/has
Moose 最重要的關鍵字可以說是 "has",它本質上是迄今為止最強大的訪問器生成器。許多這些功能可以在 Moose 和 Class::MOP::Accessor 的附帶文件中找到。
下面你可以找到所有 has 的屬性。[1] 星號代表預設值。
[+]has => (
isa => $type_constraint,
is => 'ro|rw',
coerce => 0*|1,
weak_ref => 0*|1,
does => $role_name,
required => 0*|1
lazy => 0*|1
auto_deref => 0*|1
init_arg => $str|undef,
default => $default_value|sub { $default_value },
metaclass => $voodoo,
trigger => $sub_ref,
initializer=> $sub_ref,
handles => Array|Hash|Regex|Role|Code,
lazy_build => 0*|1,
clearer => $clearer_name,
predicate => $predicate_name,
builder => $builder_name,
reader => $reader_name,
writer => $writer_name,
);
has 的第二個最重要的屬性是 isa。isa 發音為 "Is a",它實際上只是告訴屬性可以被什麼設定。
Any
Item
Bool
Undef
Defined
Value
Num
Int
Str
ClassName
Ref
ScalarRef
ArrayRef
HashRef
CodeRef
RegexpRef
GlobRef
FileHandle
Object
Role
這些是 isa 的預設選擇。除了它們之外,isa 可以設定為任何類名,例如 isa => "URI" 或 isa => "HTML::Treebuilder"。
has 'foobar' => (
isa => 'Str',
is => 'ro',
);
has 'foobar' => (
isa => 'MooseObject',
is => 'ro',
);
Moose v0.26 引入了引數化型別[1]——沒有人知道它們到底是什麼,但它們提供了執行以下操作的能力
has 'foobar' => (
isa => 'ArrayRef[URI]'
);
現在當您嘗試儲存到 ->foobar 時,您最好只向 ArrayRef 中新增 URI 元素,否則您將 croak。
has 生成屬性的核心是 is 屬性。如果某物是 is => 'ro',使用者將擁有一個指定名稱的getter(只讀訪問器)。相反,如果提供的 value 是 is => 'rw',使用者將擁有一個 getter/setter 混合(讀寫訪問器)。
我們將 is 放在 isa 之後,因為這兩個是 has 最常用的屬性。如果按此順序排列,並且您使用 PBPs 的逗號左對齊樣式,它們將對齊,您的程式碼將使 Rubyists 流口水。當他們走開時,您可以立即將其切換回混淆模式。 |
預設值是不生成任何型別的訪問器。
has 的 coerce 屬性比 is 和 isa 複雜得多。它告訴 Moose,“嘿,老兄,如果你能把這個值變成我想要的資料型別,而不用一直這麼麻煩,那會很酷。”
Moose 通常會聽你的。
package MyMoose;
use Moose;
use Moose::Util::TypeConstraints;
use URI;
subtype 'URI'
=> as 'Object'
=> where { $_->isa('URI') };
coerce 'URI'
=> from 'Str'
=> via { URI->new($_) };
has 'myuri' => ( is => 'rw', isa => 'URI', coerce => 1 );
package main;
my $m = MyMoose->new;
$m->myuri( 'foobar' );
print ref $m->myuri; ## print URI::_generic
要使用 coerce,您必須提供一個可以強制轉換的子型別。
如果您設定了 weak_ref => 1,則不能使用 coerce => 1。
Weak_ref 阻止屬性中持有的引用計數被物件遞增。這意味著,如果您有一個副本,並且一百個物件使用它並且 weak_ref => 1,您可以透過 undef'ing 您的一個副本成功地銷燬它們。當 Perl 的垃圾收集器的引用計數達到零時,它將銷燬該專案。使用 weak_ref,您的物件永遠不會增加引用計數。這阻止了迴圈遞迴,但使您容易受到“從腳下撤走地毯”的風險,可以這麼說。
package MyMoose;
use Moose;
has 'foo' => (
isa => 'ArrayRef',
is => 'ro',
weak_ref => '1',
);
package main;
my $foo = [qw/ foo bar baz/];
my $m = MyMoose->new({ foo => $foo });
print @{ $m->foo }; ## prints foobarbaz
undef $foo;
## Can't use an undefined value as an ARRAY reference at test.pl line 21.
print @{ $m->foo };
這將接受儲存在這個屬性中的值預期已使用過的角色的名稱。
| 此部分是一個存根。 您可以透過 擴充套件它 來幫助華夏公益教科書。 |
required 的內容是 required => 1。所有必需的內容都必須在傳送到 ->new 的雜湊的引用中指定一個值。
package MyMoose;
use Moose;
has 'foo' => ( required => 1 );
package main;
my $m1 = MyMoose->new(); ## dies with error pinpointing cause
my $m2 = MyMoose->new({ foo => 4 }); ## Blisses in the fact the user is not a git
不想在編譯時發生的事情... 延遲它!Lazy 需要一個預設值:您必須提供一個。
這個屬性引入了一種全新的建模問題的方式。它帶來了一種新的思維方式,不要做工作,直到需要完成。計算機程式終於有了拖延的能力,而不會讓我感到壓力。我討厭將這種奢侈的權利限制在自己身上。
將 lazy/default 組合視為一個子程式,該子程式儲存其返回值,因此它不必在後續呼叫中重新計算它。
在這個示例中,foobar 直到您請求它才會被設定;並且,在這個示例中,它會拉取一個網頁,因此我們不想在知道我們需要結果之前執行這個可能很耗時的操作。
sub build_foobar { return get( 'http://www.example.com/somedatafeed' ); }
has 'foobar' => (
isa => Int,
is => 'rw',
lazy => 1,
default => \&build_foobar,
);
default 中所有子程式的build_ 字首是一個 Moose 約定,而不僅僅是本教程的一個怪癖——一些功能依賴於它。 |
Auto_deref 是一種狡猾且具有欺騙性的東西。它不能修復 Perl,它只是讓事情變得稍微簡單一些。這個屬性只是為 ArrayRef 返回一個擴充套件的 Array,或為 HashRef 返回一個 Hash。要使用auto_deref您必須有 isa=>ArrayRef 或 isa=>HashRef 或兩者之一的引數化型別:isa=>ArrayRef[Int]
has 'foo' => ( isa => 'HashRef', is => 'rw', auto_deref => 1 );
我的意思是欺騙性是什麼意思呢auto_deref仍然只是簡單地按值傳遞。
package Class;
use Moose;
has 'foo' => ( isa => 'ArrayRef', is => 'rw', auto_deref => 1 );
my $c = Class->new({ foo => [qw/foo bar baz/] });
## Uses auto_deref
s/.*// for $c->foo;
print $_ for $c->foo; # foo bar baz
## Uses manual deref
s/.*// for @{$c->foo};
print $_ for $c->foo; # Nada.
Default 具有兩種截然不同的功能
- 設定一個預設的靜態值。
- 在第一次呼叫訪問器時,設定一個動態 lazy(
lazy=>1)預設值
Default 的第一個也是最簡單的應用,只是設定一個回退靜態值
package Person;
use Moose;
has 'name' => (
isa => 'Str',
is => 'rw',
default => "I'm too stupid to fill out a form",
);
package main;
my $p = Person->new({ name => $_ });
print 'Greetings' . $p->name;
Default 的第二個實現要複雜得多。使用這種方法,您將屬性宣告為 lazy => 1(在編譯時不執行任何操作),然後將 default 指向一個函式,例如 sub { DBI->connect }。當該函式被呼叫時,它會看到該插槽未設定(透過 predicate 測試失敗),然後呼叫 default 指向的 sub,用返回值設定該插槽。該值被快取起來,除非該插槽再次變為未設定狀態——就像呼叫 clearer 的情況一樣,否則不會重新計算。
| 此部分是一個存根。 您可以透過 擴充套件它 來幫助華夏公益教科書。 |
如果您需要撤消對值所做的所有更改,clearer是一個很好的方法。假設您已經對以下內容進行了第一次呼叫:lazy => 1屬性,它將初始值設定為相應的default. 假設您不喜歡此值,並且希望將其重置為從未呼叫過一樣。在這種情況下,呼叫clearer指定的 方法將實現此目的。在內部,clearer會刪除該槽位。
如果您在cleared 值上呼叫訪問器,它將返回undef,除非存在lazy/default組合指定。在這種情況下,它將重新初始化,就像第一次呼叫一樣。如果您呼叫clearer在一個普通的default上,值可能看起來設定為undef. 事實上,該槽位不存在。要測試這一點,請參見 謂詞。
package MyMoose;
use Moose;
has 'foo' => (
isa => 'Int',
is => 'rw',
clearer => 'clear_foo',
);
package main;
my $m = MyMoose->new;
$m->foo(5);
$m->clear_foo;
print "defined" if defined $m->foo;
print "exists" if exists $m->{foo};
print $m->{foo};
Use of uninitialized value in print at test.pl line 22.
切勿訪問 Moose 物件的底層雜湊,除非您正在為書籍編寫醜陋的示例。Moose 應該抽象出它所祝福的內容。不要讓人想起雜湊的存在。
謂詞所做的只是建立一個子例程,如果屬性的相應槽位存在,則返回真 (1),如果不存在,則返回假 (0)。這是一個簡單的便利屬性。如果沒有predicate => 'name',您將無法輕鬆區分該槽位是否存在,而不會違反面向物件的黑盒原則。謂詞使此步驟變得容易,並允許您繼續而無需檢視 Moose 內部。謂詞僅對以下情況有用:
- 與它的家族函式
clearer->(它刪除槽位)進行互動。在該上下文中,可以將謂詞視為clearer_test。 - 測試是否向建構函式提供了非必需屬性。[2]
- 測試具有
default的lazy => 1是否已觸發
謂詞接受一個字串,它用來生成謂詞子例程的名稱
predicate => 'my_predicate_sub'
在這裡我們將看到它的實際應用
package MyMoose;
use Moose;
# To accept undef also as a valid data.
has 'ulcer' => (
isa => 'Str|Undef',
is => 'rw',
predicate => 'is_sick',
);
package main;
my $m = MyMoose->new({ ulcer => 'painful' });
print $m->is_sick # true
## User doesn't know affliction
## Here Ulcer description isn't defined but we still have an ulcer
my $m = MyMoose->new({ ulcer => undef });
print $m->is_sick # true
觸發器是一個函式,在您寫入訪問器或使用建構函式設定值後呼叫它。但是,在撰寫本文時,它們不會在使用以下內容設定的屬性上觸發:lazy_build, default或builder. 使用以下內容設定觸發器:CodeRef,它接收以下值:$self、$value 和 $oldValue。
use Moose;
sub shout { print 'Moosegasm' }
has 'foo' => ( is => 'rw', trigger => \&shout );
如果您想要一個觸發器僅在您執行設定器時執行,請嘗試更接近以下內容:[3]
after 'foo' => sub {};
after $self->meta->get_attribute('foo')->get_write_method => sub {}
如果您希望觸發器在從構建器/預設值/lazy_build 構建的內容上觸發,您要麼必須在構建器/預設值/lazy_build 程式碼中顯式展開觸發器,要麼在構建器周圍使用一個包裝器。這是因為觸發器可以假定屬性已設定並從中讀取。但是,Moose 僅在構建器返回後設置屬性,因此您不能簡單地從構建器內部呼叫觸發器。
around '_build_foo' => sub {
my ( $sub, $self, @args ) = @_;
my $foo = $self->$sub;
$self->foo( $foo );
$self->_init_foo;
$foo;
};
此屬性有兩個不同的用途
- 它可以改變 Moose 用於設定屬性的建構函式雜湊中的鍵。
- 您可以透過將其設定為 undef 來忽略建構函式中傳送的任何值。
您可以透過顯式設定來告訴 Moose 在建構函式的雜湊 (->new( $constructorsHash )) 中使用不同的鍵init_arg為屬性名稱以外的任何內容。例如
package Class;
use Moose;
has 'foo' => ( isa => 'Str', is => 'rw', init_arg => 'bar' );
package main;
say Class->new({ bar => "BarValue" })->foo ## outputs "BarValue"
或者,您可以讓 Moose 忽略建構函式的雜湊。即使使用預設值,這也將起作用。
package Class;
use Moose;
has 'foo' => ( isa => 'Bool', is => 'rw', default => 1, init_arg => undef );
package main;
say Class->new({ foo => 0 })->foo; ## returns true
此屬性不能被繼承,也不能使用 +attr 修改
package Class;
use Moose::Role;
has 'foo' => ( isa => "Int", is => "ro" );
has "+foo" => (init_arg => "FoO");
Class->new({FoO=>1})
此功能有助於初始化(設定槽位)。當滿足以下條件之一時,它會觸發:
- 存在預設值
- 建構函式中傳送了一個值
- 從 lazy_build 計算出一個值
- 從構建器計算出一個值
- 或者,透過元資料觸發,例如Class::MOP::Attribute的set_initial_value
如果您沒有在初始化器中設定槽位,該槽位將有效地設定為undef,假設型別系統允許它。
初始化器子例程接收四個值:$self、$value、$writerSubRef 和 $attributeMeta。其中 $attributeMeta 可能是 Class::MOP::Attribute 的例項。$writerSub 不是一個方法。
關於初始化器的一個非常重要的點是,它在型別系統之後觸發。該值必須透過型別強制或成為有效型別才能在初始化器觸發之前。
package MooseClass;
use Moose;
has "foo" => (
isa => 'Value',
is => 'rw',
initializer => sub {
my ( $self, $value, $writer_sub_ref, $attribute_meta ) = @_;
$writer_sub_ref->($value);
}
);
my $c = MooseClass->new( { foo => 5 } );
say $c->foo;
Lazy_build 是一種屬性,旨在簡化類的快速構造。以下是它的組成部分:[4]
#If your attribute name starts with an underscore:
has '_foo' => (lazy_build => 1);
#is the same as
has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', builder => '_build__foo');
# or
has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', default => sub{shift->_build__foo});
#If your attribute name does not start with an underscore:
has 'foo' => (lazy_build => 1);
#is the same as
has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', builder => '_build_foo');
# or
has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', default => sub{shift->_build_foo});
此功能提供另一種將方法委託給其他模組的方法。
該handles屬性可以用不同的方式配置,具有不同的語法糖度
- 陣列
- 雜湊
- 正則表示式
以下來自 HTML::TreeBuilderX::ASP_NET 的示例使用正則表示式配置
has 'hrf' => (
isa => 'HTTP::Request::Form',
is => 'ro',
handles => qr/.*/,
lazy_build => 1,
);
一個好的通用用例是handles => qr/.*/,它告訴 Moose 將所有在您的包中宣告但未重寫的函式委託給在isa中指定的模組。這通常可以避免子類化和修改 ->new。
Traits,更準確地說是屬性 Traits,是一種擴充套件屬性功能的機制。
一個例子是 Moose 透過以下內容提供的功能:Nativetraits。這些將為所有容器型別提供幫助程式方法雜湊, 陣列;以及強型別Number, String, Bool, Couter和Code. Native traits 具有可以透過額外的handles屬性按需開啟的功能。
has 'attributes' => (
isa => 'HashRef',
traits => ['Hash'],
is => 'ro',
handles => { get_attr => 'get' },
);
初始化器是預設過程的一部分。設定default的層級與觸發初始化器的層級相同。預設值會將槽位初始化為設定的值。觸發順序defined和初始化器對於屬性是已定義的;但是,對於所有模組屬性來說是完全未定義的。
對於屬性已定義
has 'foo' => (
isa => 'Str'
, is => 'rw'
, default => 'foobarbaz'
, initializer => sub {
my ( $self, $arg, $writerSub, $attributeMeta ) = @_;
print $arg; # prints 'foobarbaz'
}
);
對於屬性集未定義:一個屬性 (foo) 可能會觸發它的初始化器,寫入另一個屬性 (bar),只是讓 (bar) 被它自己的預設值覆蓋。
has 'bar' => ( isa => 'Str', is => 'rw', default => 'bar' )
has 'foo' => (
isa => 'Str'
, is => 'rw'
, default => 'foobarbaz'
, initializer => sub {
my ( $self, $arg, $writerSub, $attributeMeta ) = @_;
$self->bar('foo');
}
);
...
print MooseObject->new->bar ## prints bar
引數
兩者觸發器和初始化器都使用 $self、$value 和 $attributeMeta。
- 觸發器
- $self、$value、$attributeMeta。
- 初始化器
- $self、$value、$writerSub、$attributeMeta。
觸發順序
觸發器在任何寫入槽位的事件中都不觸發可能被認為是一個錯誤。
| 條件 | 示例 | 觸發器 | 初始化器 |
|---|---|---|---|
| 建構函式 | Class->new({foo=>1}) | 觸發 | 觸發 |
| lazy_build | Class->new->foo | 無 | 觸發 |
| builder | Class->new | 無 | 觸發 |
| default | Class->new | 無 | 觸發 |
| 執行時顯式 | Class->new->foo(5) | 觸發 | 無 |
共存
如果一個觸發器可用,初始化器將始終在觸發器之前觸發。但是,它仍然不會在執行時顯式將值設定為插槽時觸發。
您可以使用觸發器在執行時顯式提供值時使子觸發。這種方法的缺點是在建構函式提供值時會觸發兩次:一次來自觸發器,一次來自初始化器.
has 'foo' => ( isa => 'Value', is => 'rw', initializer => \&bar, trigger => \&bar );
sub bar { stuff }
- ^ 由於 Moose 的繼承以及缺乏外部文件,幾乎不可能確定我們是否列出了所有內容。例如,在官方 Moose 文件中,沒有提到
->clear或->predicate;但是,它們在Class::MOP::Attribute中有記錄。
- ↑ Little, Stevan (2007-09-27). "Changes/Revision History". Retrieved 2007-10-20.
- ↑ 以及必要的那些,但如果沒有提供,則會 die()
- ↑ 感謝 irc.perl.org 的 Sartak 提供的資訊
- ↑ Little, Stevan. "Moose::Meta::Attribute". Moose 0.32. Infinity Interactive.
{{cite web}}: Unknown parameter|access_date=ignored (help); Unknown parameter|coauthors=ignored (|author=suggested) (help)