我有一个calc
方法的示例对象:
package A;
sub new {...}
sub calc {
my ($self, $a, $b) = @_;
return {first => $a, second => $b, sum => $a + $b}
}
简单用法:
my $result = A->new->calc(1, 2);
print 'Result is ', $result->{sum}; # output: Result is 3
现在,我想添加一个链接方法log
,以便输出计算参数并返回结果:
package A;
...
sub calc {
...
return $self->{result} = {...}
}
sub log {
my $self = shift;
print sprintf 'a = %d, b = %d', $self->{result}->{first}, $self->{result}->{second};
return $self->{result};
}
并像这样使用它:
my $result = A->new->calc(10, 20);
print "Result of calc: ", $result->{sum}; # output: 30
$result = A->new->calc(11, 12)->log; # output: a = 11, b = 12
print 'Result is ', $result->{sum}; # output: Result is 23
我尝试使用带有重载的helper对象,但我的calc
可以返回非常不同的结构,如标量,数组,arrayref,hashref ......所以,我的助手的目标代码非常糟糕且错误。
现在,我有两个问题:
$self
返回calc
而不是结果。答案 0 :(得分:2)
我认为不可能(如果是的话,我不想使用它)。
链式方法的习惯用法通常与改变对象的方法一起使用。因此,如果您想以这种方式编写它,calc()
应始终返回该对象,您应该有一个单独的方法来返回结果。然后清楚每种方法正在做什么。
A->new()->calc(10, 20)->result();
A->new()->calc(10, 20)->log()->result();
不管怎样,不是每个人都是链接方法的粉丝。如果我正在接近同样的问题,我可能会在对象上有一个详细的属性:
A->new(verbose => 1)->calc(10, 20);
并根据执行计算的方法中的日志进行记录(可能会节省将所有中间计算提交给私有成员的麻烦)。但要么是有效的,要么根据计算可能更好。
答案 1 :(得分:1)
所以你希望calc
方法返回一个hashref,除了,当它被称为:
$object->calc(...)->some_other_method;
......在这种情况下,它需要返回$object
本身?
我的第一个想法是,这绝对是一个API。
我的第二个想法是你应该能够用Want完成这个任务。但是我的良好品味使我无法提供代码样本。
答案 2 :(得分:0)
在功能上执行所需操作的最简单方法可能是向对象添加类似_last_action
属性的内容,并添加内部方法来填充该属性。然后,每个计算方法只需要使用您需要的数据调用该填充方法来表示计算。 ->log
方法只需要拉出并处理该数据,您的计算方法可以保持相当干净,只需返回所需内容。我在下面做过类似的事情。
也就是说,你的界面不是最容易使用的,如果你的处理变得复杂,它会增加很多开销来计算你可能永远不会使用的结果(因为你好像用一个非常通用的名字来计算它) calc
)。不,不可能确定你在调用链中的距离。
我不完全确定你在这里想要达到的目标,但是这就是我如何解决我认为你想要做的事情......并注意我使用Moose
因为它(或{{ 1}}或Moo
)使OO变得更容易,操作符重载处理错综复杂。我也使类不可变(一旦设置它不会改变,你得到一个新的对象),因为这通常是一个更清洁的界面,更容易维护。
Mouse
一个小测试程序:
package MathHelper;
use Moose;
# our basic math operations
use overload fallback => 1,
'+' => 'plus',
'-' => 'minus',
'<=>' => 'compare',
'0+' => 'to_number',
'""' => 'to_number',
;
# allow for a ->new( $value ) call
around BUILDARGS => sub {
my $orig = shift;
my $self = shift;
if ( @_ == 1 && !ref $_[0] ) {
return $self->$orig( value => $_[0] );
} else {
return $self->$orig( @_ );
}
};
has value => (
is => 'ro',
default => 0,
documentation => 'Represents the internal value',
);
has _last => (
is => 'ro',
default => undef,
init_arg => 'last',
documentation => 'The last calculation performed',
);
sub last {
my $self = shift;
return $self->_last if defined $self->_last;
return 'No last result';
}
sub plus {
my ( $self, $other, $swap ) = @_;
my $result = $self->value + $other;
return __PACKAGE__->new(
value => $result,
last => "$self + $other = $result",
);
}
sub minus {
my ( $self, $other, $swap ) = @_;
my $result = $self->value - $other;
$result = -$result if $swap;
return __PACKAGE__->new(
value => $result,
last => ( $swap ) ? "$other - $self = $result" : "$self - $other = $result",
);
}
sub compare {
my ( $self, $other, $swap ) = @_;
if ( $swap ) {
return $other <=> $self->value;
} else {
return $self->value <=> $other;
}
}
sub to_number {
my ( $self ) = @_;
return $self->value;
}
__PACKAGE__->meta->make_immutable;
1;
输出:
#!/usr/bin/env perl
use Modern::Perl;
use MathHelper;
my $ten = MathHelper->new( 10 );
my $twenty = MathHelper->new( 20 );
my $thirty = $ten + $twenty;
say "\$ten is $ten";
say "\$twenty is $twenty";
say "\$thirty is $thirty [".$thirty->last."]";
my $tmp = $twenty - $ten;
say "\$ten - \$twenty = $tmp [".$tmp->last."]";
$tmp = $twenty - 3;
say "\$twenty - 3 = $tmp [".$tmp->last."]";
$tmp = $ten - $twenty;
say "\$twenty - \$ten = $tmp [".$tmp->last."]";
$tmp = 3 - $twenty;
say "3 - \$twenty = $tmp [".$tmp->last."]";
say "\$ten is equal to 10" if 10 == $ten;
say "\$ten is smaller than \$twenty" if $ten < $twenty;
say "\$twenty is larger than \$ten" if $twenty > $ten;
say "\$ten + \$twenty is equal to \$thirty" if $ten + $twenty == $thirty;
say "\$ten + \$twenty - 1 is not equal to \$thirty" if $ten + $twenty - 1 != $thirty;
免责声明:我的代码可能仍然存在错误,并且还有很大的改进空间......但它是一个开始,而且根据我的经验,这是唯一可行的解决方案。我在$ten is 10
$twenty is 20
$thirty is 30 [10 + 20 = 30]
$ten - $twenty = 10 [20 - 10 = 10]
$twenty - 3 = 17 [20 - 3 = 17]
$twenty - $ten = -10 [10 - 20 = -10]
3 - $twenty = -17 [3 - 20 = -17]
$ten is equal to 10
$ten is smaller than $twenty
$twenty is larger than $ten
$ten + $twenty is equal to $thirty
$ten + $twenty - 1 is not equal to $thirty
生产中使用了非常相似的内容(该代码有多个内部值,但$work
和months
)。
答案 3 :(得分:0)
方法无法检测它是否是一连串调用中的最后一个调用 - 并且有充分理由:$x->a->-b>-c
应该与do { my $y = $x->a; my $z = $y->b; $z->c }
的行为相同。我建议你选择不同的API:
my $result = A->log($instance->a->b->c);
此处的日志记录将由单独的(类)方法执行。这是一个更清洁的设计,实现起来很简单:
sub log :method {
my ($class, @results) = @_;
...; # print out the results
return @results;
}
不要仅返回结果的一部分 - 日志记录不应干扰正常的数据流。有一个问题:必须在列表上下文中调用->c
方法,这就是为什么可能会返回多个结果的原因。无法将正确的上下文传播到->c
方法。要做到这一点,我们必须使用一个闭包:
my $result = $instance->log(sub{ $_->a->b->c });
这可以实现为
sub log :method {
my ($self, $action) = @_;
local $_ = $self;
my @results = (wantarray) ? $action->($self) : scalar $action->($self);
...; # perform actual logging
return (wantarray) ? @results : $results[0];
}
我认为这是最好的解决方案,因为它不会产生任何令人惊讶的语义。
my $result = $instance->a->b->log->c;
此处,{<1}}将在记录结果的方法之前调用。这可以使用两种策略来实现:
第一个解决方案是在对象中保留内部标志。此标志将由log
设置。执行下一个方法时,将在返回之前检查该标志。如果已设置,将执行日志记录:
log
这是一个相当理智的实现,但需要更改可能记录的所有方法。
其他实施策略是通过代理对象。代理程序包装执行实际行为的对象,但它将记录所有访问:
sub log :method {
my ($self) = @_;
$self->{_log_next_call} = 1;
return $self;
}
sub _do_logging {
my ($self, @data) = @_;
$self->{_log_next_call} = 0;
...; # log the data
}
sub c {
...; # do normal stuff
$self->_do_logging($result) if $self->{_log_next_call};
return $result;
}
然后package Proxy {
sub new {
my ($class, $obj) = @_;
return bless \$obj => $class;
}
# override "DOES" and "can" for correct proxying
sub DOES {
my ($self, $role) = @_;
...; # validate that $self is an object
return $$self->DOES($role);
}
sub can {
my ($self, $method) = @_;
...; # validate that $self is an object
my $code = $$self->can($method);
return undef unless defined $code;
return sub {
my @result = (wantarray) ? $code->(@_) : scalar $code->(@_);
...; # log the result
return (wantarray) ? @result : $result[0];
};
}
# the AUTOLOAD method does the actual proxying,
# although the interesting stuff is done in "can"
sub AUTOLOAD {
my $self = shift;
my $method = our $AUTOLOAD;
$method =~ s/\A.*:://s;
my $code = $self->can($method);
...; # throw error if $code is undef
return $code->($$self, @_);
}
}
方法只构造代理:
log