perl – 用Moose做对象组合的最佳方法是什么?
关于Moose最佳实践的初学者问题:
从简单的“点”示例开始,我想构建一个“线” – 对象,由两个点组成,并具有长度属性,描述起点和终点之间的距离. { package Point; use Moose; has 'x' => ( isa => 'Int',is => 'rw' ); has 'y' => ( isa => 'Int',is => 'rw' ); } { package Line; use Moose; has 'start' => (isa => 'Point',is => 'rw',required => 1,); has 'end' => (isa => 'Point',); has 'length' => (isa => 'Num',is => 'ro',builder => '_length',lazy => 1,); sub _length { my $self = shift; my $dx = $self->end->x - $self->start->x; my $dy = $self->end->y - $self->start->y; return sqrt( $dx * $dx + $dy * $dy ); } } my $line = Line->new( start => Point->new( x => 1,y => 1 ),end => Point->new( x => 2,y => 2 ) ); my $len = $line->length; 上面的代码按预期工作. >这是解决问题/做简单对象组合的最佳方法吗? > my $line2 = Line->new( start->x => 1,start->y => 1,y => 2 ) ); >如何更改坐标时触发长度的自动重新计算?或者,拥有像“长度”这样可以“轻松”从其他属性派生的属性是没有意义的吗?这些值(长度)应该更好地作为函数提供吗? > $line->end->x(3); $line->end->y(3); $len = $line->length; >我怎样才能做出这样的事情呢?什么是一次改变点的方法 – 而不是改变每个坐标? > $line2->end(x => 3,y =>3); 谢谢你的回答! 解决方法
如果不知道你将要用它做什么,这太主观了,而且问题过于简单化了.但我可以说你正在做的事情没有错. 我所做的改变是移动工作来计算两点之间的距离为Point.然后其他人可以利用. # How do I do something like this? my $line2 = Line->new( start->x => 1,y => 2 ) ); 我要注意的第一件事是你不是通过前面的对象来节省很多打字……但就像我说这是一个简单的例子所以让我们假设使对象变得单调乏味.有很多方法可以获得你想要的东西,但一种方法是编写一个转换参数的BUILDARGS方法.手册中的示例有点奇怪,这是一个更常见的用法. # Allow optional start_x,start_y,end_x and end_y. # Error checking is left as an exercise for the reader. sub BUILDARGS { my $class = shift; my %args = @_; if( $args{start_x} ) { $args{start} = Point->new( x => delete $args{start_x},y => delete $args{start_y} ); } if( $args{end_x} ) { $args{end} = Point->new( x => delete $args{end_x},y => delete $args{end_y} ); } return %args; } 还有第二种方法可以使用类型强制来实现,在某些情况下更有意义.请参阅下面的$line2-> end(x => 3,y => 3)的答案.
奇怪的是,有一个触发器!当该属性更改时,将调用属性上的触发器.正如@Ether指出的那样,你可以添加一个clearer到长度,然后触发器可以调用未设置的长度.这不违反只读的长度. # You can specify two identical attributes at once has ['start','end'] => ( isa => 'Point',is => 'rw',required => 1,trigger => sub { return $_[0]->_clear_length; } ); has 'length' => ( isa => 'Num',is => 'ro',builder => '_build_length',# Unlike builder,Moose creates _clear_length() clearer => '_clear_length',lazy => 1 ); 现在,无论何时设置开始或结束,它们都将清除长度值,使其在下次调用时重建. 这确实会出现问题……如果修改了开始和结束,长度会发生变化,但是如果使用$line-> start-> y(4)直接更改Point对象会怎么样?如果你的Point对象被另一段代码引用并且它们改变了怎么办?这些都不会导致长度重新计算.你有两个选择.首先是使长度完全动态,这可能是昂贵的. 第二种是将Point的属性声明为只读.您可以创建一个新对象,而不是更改对象.然后,它的值无法更改,您可以安全地根据它们缓存计算.逻辑延伸到Line和Polygon等等. 这也让您有机会使用Flyweight模式.如果Point是只读的,那么每个坐标只需要一个对象. Point-> new成为工厂要么创建新对象要么返回现有对象.这可以节省大量内存.同样,这个逻辑延伸到Line和Polygon等等. 是的,将长度作为属性是有意义的.虽然它可以从其他数据派生,但您希望缓存该计算.如果Moose有一种方法可以明确地声明长度纯粹是从开始和结束派生的,那么应该会自动缓存并重新计算,但事实并非如此.
实现这一目标的最不实用的方法是使用type coercion. use Moose::Util::TypeConstraints; subtype 'Point::OrHashRef',as 'Point'; coerce 'Point::OrHashRef',from 'HashRef',via { Point->new( x => $_->{x},y => $_->{y} ) }; 然后将开始和结束的类型更改为Point :: OrHashRef并启用强制. has 'start' => ( isa => 'Point::OrHashRef',coerce => 1,); 现在start,end和new将接受散列引用并将它们静默地转换为Point对象. $line = Line->new( start => { x => 1,y => 1 },y => 2 ) ); $line->end({ x => 3,y => 3 ]); 它必须是散列引用,而不是散列,因为Moose属性只接受标量. 什么时候使用类型强制,什么时候使用BUILDARGS?一个好的 在这里,它们一起进行了一些测试. { package Point; use Moose; has 'x' => ( isa => 'Int',is => 'rw' ); has 'y' => ( isa => 'Int',is => 'rw' ); use Moose::Util::TypeConstraints; subtype 'Point::OrHashRef',as 'Point'; coerce 'Point::OrHashRef',y => $_->{y} ) }; sub distance { my $start = shift; my $end = shift; my $dx = $end->x - $start->x; my $dy = $end->y - $start->y; return sqrt( $dx * $dx + $dy * $dy ); } } { package Line; use Moose; # And the same for end has ['start','end'] => ( isa => 'Point::OrHashRef',trigger => sub { $_[0]->_clear_length(); return; } ); has 'length' => ( isa => 'Num',clearer => '_clear_length',lazy => 1,default => sub { return $_[0]->start->distance( $_[0]->end ); } ); } use Test::More; my $line = Line->new( start => { x => 1,end => Point->new( x => 2,y => 2 ) ); isa_ok $line,"Line"; isa_ok $line->start,"Point"; isa_ok $line->end,"Point"; like $line->length,qr/^1.4142135623731/; $line->end({ x => 3,y => 3 }); like $line->length,qr/^2.82842712474619/,"length is rederived"; done_testing; (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- java – 如何使Spring RestTemplate PATCH请求
- (Easy) Flipping an Image LeetCode
- Thinking in BigData(五)大数据之统计学与数据
- (第七场)A Minimum Cost Perfect Matching 【位
- perl中pop与push函数
- perl 函数 左值属性
- 如何从VB.net(或C#)调用sqlserver函数?是否有一
- [VB.NET]如何来优化sqldatareader的读取速度
- AnsiQuotedStr、AnsiExtractQuotedStr、AnsiDequ
- golang 数组(array)与切片(slice)