从简单的“点”示例开始,我想构建一个“线” – 对象,由两个点组成,并具有长度属性,描述起点和终点之间的距离.
{ 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);
谢谢你的回答!
解决方法Is this the best way to solve the problem to do simple object
composition?
如果不知道你将要用它做什么,这太主观了,而且问题过于简单化了.但我可以说你正在做的事情没有错.
我所做的改变是移动工作来计算两点之间的距离为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)的答案.
How can I trigger an automatic recalculation of length when
coordinates are changed?
奇怪的是,有一个触发器!当该属性更改时,将调用属性上的触发器.正如@Ether指出的那样,你可以添加一个clearer到长度,然后触发器可以调用未设置的长度.这不违反只读的长度.
# You can specify two IDentical attributes at oncehas ['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有一种方法可以明确地声明长度纯粹是从开始和结束派生的,那么应该会自动缓存并重新计算,但事实并非如此.
How can I make something like this possible?
$line2->end(x => 3,y => 3);
实现这一目标的最不实用的方法是使用type coercion.
您定义了一个子类型,它将散列引用转换为Point.它的
最好在Point中定义它,而不是line,以便其他类可以
使用积分时使用它.
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?一个好的
经验法则是如果参数新映射到属性,则使用类型
强迫.然后new和属性可以一致地执行,其他类可以使用该类型使其Point属性行为相同.
在这里,它们一起进行了一些测试.
{ 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;总结
以上是内存溢出为你收集整理的perl – 用Moose做对象组合的最佳方法是什么?全部内容,希望文章能够帮你解决perl – 用Moose做对象组合的最佳方法是什么?所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)