gpt4 book ai didi

perl - 使用 Moose 进行对象组合的最佳方法是什么?

转载 作者:行者123 更新时间:2023-12-05 00:35:46 26 4
gpt4 key购买 nike

只是关于 Moose 最佳实践的初学者问题:

从简单的“点”示例开始,我想构建一个“线” - 对象,由两个点组成并具有 lenght 属性,描述起点和终点之间的距离。

{
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;

上面的代码按预期工作。
现在我的问题:
  • 这是解决问题/进行简单对象组合的最佳方法吗?
  • 有没有另一种方法来创建这样的行(示例不起作用!)(顺便说一句:还有哪些其他方法确实存在?):

  • >
    my $line2 = Line->new( start->x => 1, start->y => 1, end => Point->new( x => 2, 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, start->y => 1,
    end => Point->new( x => 2, 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 指出的,您可以添加 clearerlength然后触发器可以调用它来取消设置 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
    );

    现在每当 startend设置它们将清除 length 中的值导致它在下一次被调用时被重建。

    这确实带来了一个问题... length如果 start 会改变和 end被修改了,但是如果 Point 对象直接用 $line->start->y(4) 改变会怎样? ?如果您的 Point 对象被另一段代码引用并且他们更改了它怎么办?这些都不会导致长度重新计算。你有两个选择。首先是制作 length完全动态的,这可能是昂贵的。

    二是声明Point的属性为只读。您不是更改对象,而是创建一个新对象。然后它的值不能改变,你可以安全地缓存基于它们的计算。逻辑扩展到 Line 和 Polygon 等。

    这也让您有机会使用享元模式。如果 Point 是只读的,那么每个坐标只需要一个对象。 Point->new成为制造新对象或返回现有对象的工厂。这可以节省大量内存。同样,此逻辑扩展到 Line 和 Polygon 等。

    是的,拥有 length 确实有意义作为属性。虽然它可以从其他数据派生,但您希望缓存该计算。如果 Moose 有办法明确声明 length 就好了纯粹来自 startend因此应该自动缓存和重新计算,但它没有。

    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} ) };

    然后更改 start的类型和 endPoint::OrHashRef并开启强制。
    has 'start' => (
    isa => 'Point::OrHashRef',
    is => 'rw',
    required => 1,
    coerce => 1,
    );

    现在 start , endnew将接受散列引用并将它们静默地转换为 Point 对象。
    $line = Line->new( start => { x => 1, y => 1 }, end => Point->new( x => 2, y => 2 ) );
    $line->end({ x => 3, y => 3 ]);

    它必须是一个散列引用,而不是一个散列,因为 Moose 属性只接受标量。

    什么时候使用类型强制,什么时候使用 BUILDARGS ?一个好的
    经验法则是,如果 new 的参数映射到属性,请使用类型
    强制。然后 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',
    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;

    关于perl - 使用 Moose 进行对象组合的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9031939/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com