Perl 使用Moose进行对象合成的最佳方法是什么?
只是一个关于驼鹿最佳实践的初学者问题: 从简单的“点”示例开始,我想构建一个“线”对象,它由两个点组成,具有一个lenght属性,描述起点和终点之间的距离Perl 使用Moose进行对象合成的最佳方法是什么?,perl,oop,moose,Perl,Oop,Moose,只是一个关于驼鹿最佳实践的初学者问题: 从简单的“点”示例开始,我想构建一个“线”对象,它由两个点组成,具有一个lenght属性,描述起点和终点之间的距离 { package Point; use Moose; has 'x' => ( isa => 'Int', is => 'rw' ); has 'y' => ( isa => 'Int', is => 'rw' ); } { package Line; use 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', is => 'rw', required => 1, );
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;
上面的代码按预期工作。
现在我的问题是:
- 这是解决问题/进行简单对象合成的最佳方法吗
- 有没有其他方法可以用这样的东西创建线条(示例不起作用!)(顺便问一下:还有哪些其他方法存在?)
- 当坐标发生变化时,如何触发长度的自动重新计算?或者,像长度这样的属性可以“轻松”从其他属性派生出来,这没有意义吗?这些值(长度)是否最好作为函数提供
- 我怎样才能使这样的事情成为可能?如何一次改变点,而不是改变每个坐标
# How do I do something like this?
my $line2 = Line->new(
start->x => 1, start->y => 1,
end => Point->new( x => 2, y => 2 )
);
我要注意的第一件事是,通过前面的对象,您并没有节省太多的打字时间。。。但是就像我说的,这是一个简单化的例子,所以让我们假设制作对象是乏味的。有很多方法可以得到你想要的,但是有一种方法是编写一个方法来转换参数。手册中的例子有点奇怪,这里有一个更常见的用法
# 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所指出的,您可以向length
添加一个触发器,然后可以调用该触发器来取消设置length
。这并不违反只读的长度
# 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
);
现在,每当设置start
或end
时,它们都会清除length
中的值,从而在下次调用时重新生成该值
这确实带来了一个问题<如果修改了开始
和结束
,则代码>长度将更改,但是如果使用$line->start->y(4)
直接更改点对象会怎么样?如果您的点对象被另一段代码引用,并且他们更改了它,该怎么办?这两种情况都不会导致重新计算长度。你有两个选择。首先是使长度
完全动态化,这可能代价高昂
第二种方法是将点的属性声明为只读。创建新对象而不是更改对象。这样,它的值就无法更改,您可以安全地缓存基于它们的计算。逻辑扩展到直线和多边形等
这也为您提供了使用Flyweight图案的机会。如果点是只读的,则每个坐标只需要一个对象Point->new
成为一个工厂,要么创建一个新对象,要么返回一个现有对象。这可以节省大量内存。同样,这种逻辑延伸到直线和多边形等
是的,将length
作为属性是有意义的。虽然它可以从其他数据派生,但您希望缓存该计算。如果Moose有办法明确声明length
纯粹是从start
和end
派生出来的,因此应该自动缓存和重新计算,那就太好了,但是它没有
我怎样才能使这样的事情成为可能<代码>$line2->end(x=>3,y=>3)代码>
要做到这一点,最简单的方法是使用。
定义一个子类型,该子类型将哈希引用转换为点。它是
最好用点而不是线来定义它,这样其他类就可以
当他们使用点时使用它
use Moose::Util::TypeConstraints;
subtype 'Point::OrHashRef',
as 'Point';
coerce 'Point::OrHashRef',
from 'HashRef',
via { Point->new( x => $_->{x}, y => $_->{y} ) };
然后将start
和end
的类型更改为Point::OrHashRef
,并启用强制
has 'start' => (
isa => 'Point::OrHashRef',
is => 'rw',
required => 1,
coerce => 1,
);
现在,start
,end
和new
将接受散列引用,并将它们静默地转换为点对象
$line = Line->new( start => { x => 1, y => 1 }, end => Point->new( x => 2, y => 2 ) );
$line->end({ x => 3, y => 3 ]);
它必须是散列引用,而不是散列,因为Moose属性只接受标量
何时使用类型强制,何时使用BUILDARGS
?好的
经验法则是,如果new的参数映射到属性,则使用type
胁迫。然后new
,属性可以一致动作,其他类可以使用该类型使其点属性动作相同
在这里,加上一些测试
{
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',
from 'HashRef',
via { Point->new( x => $_->{x}, 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',
coerce => 1,
is => 'rw',
required => 1,
trigger => sub {
$_[0]->_clear_length();
return;
}
);
has 'length' => (
isa => 'Num',
is => 'ro',
clearer => '_clear_length',
lazy => 1,
default => sub {
return $_[0]->start->distance( $_[0]->end );
}
);
}
use Test::More;
my $line = Line->new(
start => { x => 1, y => 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;
这与其说是一个驼鹿问题,不如说是一个面向对象的设计问题。但在这些方面,有几件事值得指出:
长度
方法
has 'start' => (
isa => 'Point::OrHashRef',
is => 'rw',
required => 1,
coerce => 1,
);
$line = Line->new( start => { x => 1, y => 1 }, end => Point->new( x => 2, y => 2 ) );
$line->end({ x => 3, y => 3 ]);
{
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',
from 'HashRef',
via { Point->new( x => $_->{x}, 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',
coerce => 1,
is => 'rw',
required => 1,
trigger => sub {
$_[0]->_clear_length();
return;
}
);
has 'length' => (
isa => 'Num',
is => 'ro',
clearer => '_clear_length',
lazy => 1,
default => sub {
return $_[0]->start->distance( $_[0]->end );
}
);
}
use Test::More;
my $line = Line->new(
start => { x => 1, y => 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;
sub new_from_coords {
my ($class, $x1, $y1, X2, $y2) = @_;
return $class->new(
start => $class->_make_point($x1, $y1),
end => $class->_make_point($x2, $y2),
);
}
sub _make_point {
my ($class, $x, $y) = @_;
return Point->new(x => $x, y => $y);
}
my $line = Line->new_from_coords(2, 3, 6, 7);