Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Perl 使用Moose进行对象合成的最佳方法是什么?_Perl_Oop_Moose - Fatal编程技术网

Perl 使用Moose进行对象合成的最佳方法是什么?

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;

只是一个关于驼鹿最佳实践的初学者问题:

从简单的“点”示例开始,我想构建一个“线”对象,它由两个点组成,具有一个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;
上面的代码按预期工作。 现在我的问题是:

  • 这是解决问题/进行简单对象合成的最佳方法吗

  • 有没有其他方法可以用这样的东西创建线条(示例不起作用!)(顺便问一下:还有哪些其他方法存在?)

>

  • 当坐标发生变化时,如何触发长度的自动重新计算?或者,像长度这样的属性可以“轻松”从其他属性派生出来,这没有意义吗?这些值(长度)是否最好作为函数提供
>

  • 我怎样才能使这样的事情成为可能?如何一次改变点,而不是改变每个坐标
>

谢谢你的回答

这是解决简单对象问题的最好方法吗 构图

这太主观了,不知道你将如何处理它就无法回答,问题过于简单化了。但我可以说你所做的没有错

我要做的更改是将计算两点之间距离的工作移动到点。然后其他人可以利用

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