2012-05-10 32 views
5

Với Moose, bạn có thể có lazybuilders trên thuộc tính, nơi xây dựng được gọi khi thuộc tính là lần đầu tiên truy cập nếu thuộc tính không sẵn có ở đó. Bạn có thể bị ép buộc loại thuộc tính với coerce, nhưng điều này được áp dụng bất cứ khi nào thuộc tính được đặt, vì vậy ngay cả khi khởi tạo đối tượng.Lazy Thuộc tính Cưỡng chế

Tôi đang tìm cách triển khai cưỡng chế ép buộc, nơi một thuộc tính có thể được điền lần đầu, nhưng chỉ bị ép buộc khi được truy cập lần đầu tiên. Điều này là quan trọng khi cưỡng chế là tốn kém.

Trong ví dụ sau, tôi sử dụng một loại công đoàn và phương pháp bổ để làm điều này:

package My::Foo; 
use Moose; 
has x => (
    is => 'rw', 
    isa => 'ArrayRef | Int', 
    required => 1 
); 

around "x" => sub { 
    my $orig = shift; 
    my $self = shift; 
    my $val = $self->$orig(@_); 
    unless(ref($val)) { 
     # Do the cocerion 
     $val = [ map { 1 } 1..$val ]; 
     sleep(1); # in my case this is expensive 
    } 
    return $val; 
}; 
1; 

my $foo = My::Foo->new(x => 4); 
is_deeply $foo->x, [ 1, 1, 1, 1 ], "x converted from int to array at call time"; 

Tuy nhiên có một vài vấn đề với điều này:

  1. Tôi không thích kiểu đoàn + phương pháp sửa đổi phương pháp. Nó đi ngược lại đề xuất "Phương pháp hay nhất" cho use coercion instead of unions. Nó không phải là khai báo.

  2. Tôi cần thực hiện việc này với nhiều thuộc tính trên nhiều lớp. Do đó một số dạng DRY là cần thiết. Đây có thể là các vai trò meta-thuộc tính, loại cưỡng chế, những gì có bạn.

Cập nhật: Tôi đi theo ikegami's gợi ý để đóng gói các loại ép buộc đắt bên trong một đối tượng và cung cấp một sự ép buộc bên ngoài để đối tượng này:

package My::ArrayFromInt; 
use Moose; 
use Moose::Util::TypeConstraints; 
subtype 'My::ArrayFromInt::Inner', 
    as 'ArrayRef[Int]'; 
coerce 'My::ArrayFromInt::Inner', 
    from 'Int', 
    via { return [ (1) x $_ ] }; 
has uncoerced => (is => 'rw', isa => 'Any', required => 1); 
has value => (
    is  => 'rw', 
    isa  => 'My::ArrayFromInt::Inner', 
    builder => '_buildValue', 
    lazy => 1, 
    coerce => 1 
); 
sub _buildValue { 
    my ($self) = @_; 
    return $self->uncoerced; 
} 
1; 
package My::Foo; 
use Moose; 
use Moose::Util::TypeConstraints; 
subtype 'My::ArrayFromInt::Lazy' => as class_type('My::ArrayFromInt'); 
coerce 'My::ArrayFromInt::Lazy', 
    from 'Int', 
    via { My::ArrayFromInt->new(uncoerced => $_) }; 
has x => (
    is => 'rw', 
    isa => 'My::ArrayFromInt::Lazy', 
    required => 1, 
    coerce => 1 
); 
1; 

này hoạt động nếu $foo->x->value được gọi. Tuy nhiên, điều này không giải quyết được điểm số 2, vì tôi sẽ cần phải tạo My::ArrayFromInt và loại phụ ::Lazy cho mỗi thuộc tính mà tôi muốn chuyển đổi. Và tôi muốn tránh gọi số $foo->x->value nếu có thể.

+1

Nếu có hai cách đại diện cho một mốc, người ta có thể nhận được một trong hai biểu diễn. Coerce vào một đối tượng, sau đó lấy dữ liệu từ đối tượng theo định dạng bạn muốn. [Ví dụ] (http://stackoverflow.com/questions/10506416/can-i-use-an-attribute-modifer-in-moose-in-a-base-class-to-handle-multiple-attri/10508753# 10508753) – ikegami

+0

s/'map {1} 1 .. $ val' /' (1) x $ val'/ – ikegami

+0

@ikegami Vấn đề là cưỡng chế là tốn kém; Tôi chỉ muốn thực hiện nó nếu thuộc tính đang được yêu cầu. – devoid

Trả lời

0

Làm thế nào về việc có typedef dọc theo dòng mô tả, sau đó làm

has _x => (
    is  => 'ro', 
    isa  => 'Int|MyArrayOfInts', 
    init_arg => 'x', 
    required => 1, 
); 

has x => (
    is => 'ro', 
    lazy => 1, 
    isa => 'MyArrayOfInts', 
    coerce => 1, 
    default => sub { $_[0]->_x }, 
); 

Nó muốn làm cho tinh thần để quấn mà lên thành một số loại phương pháp helper để tạo ra các cặp đối tượng dọc theo dòng của

has_lazily_coerced x => (
    is => 'ro', 
    isa => 'TargetType', 
); 

sẽ nhìn vào TargetType để có danh sách các loại pháp lý cho thuộc tính bóng không được ép buộc và tạo cặp thuộc tính cho bạn.

Các vấn đề liên quan