我正在练习Kata Nine:回到 Perl 的CheckOut,同时也尝试第一次使用Moose.
到目前为止,我已经创建了以下类:
package Checkout;
# $Id$
#
use strict;
use warnings;
our $VERSION = '0.01';
use Moose;
use Readonly;
Readonly::Scalar our $PRICE_OF_A => 50;
sub price {
my ( $self, $items ) = @_;
if ( defined $items ) {
$self->{price} = 0;
if ( $items eq 'A' ) {
$self->{price} = $PRICE_OF_A;
}
}
return $self->{price};
}
__PACKAGE__->meta->make_immutable;
no Moose;
1;
Run Code Online (Sandbox Code Playgroud)
整个price方法感觉不是非常愚蠢,我觉得这可以进一步重构.
有没有人对如何改进这方面有任何意见?
我注意到的第一件事是你没有为你的类使用显式属性.
$self->{price} = 0;
Run Code Online (Sandbox Code Playgroud)
这违反了使用Moose提供的大部分封装.Moose解决方案至少会price成为一个实际属性.
use 5.10.0; # for given/when
has '_price' => ( is => 'rw' );
sub price {
my ( $self, $item ) = @_;
given ($item) {
when ('A') { $self->_price($PRICE_OF_A) }
default { $self->_price(0) }
}
}
Run Code Online (Sandbox Code Playgroud)
但是,您提供的代码的主要问题是您并没有真正模拟Kata描述的问题.首先,Kata明确声明您需要在每次调用Checkout对象时传递定价规则.所以我们知道我们需要将这个状态保存在一系列ReadOnly类变量之外.
has pricing_rules => (
isa => 'HashRef',
is => 'ro',
traits => ['Hash'],
default => sub { {} },
handles => {
price => 'get',
has_price => 'exists'
},
);
Run Code Online (Sandbox Code Playgroud)
你会看到这里的price方法现在是一个使用Moose的"Native Attributes"委托的委托.这将在Item和Rule之间执行查找.但是,这不会处理对不存在的元素进行查找的情况.偷看Kata提供的单元测试之一正是这种查找:
is( price(""), 0 ); # translated to Test::More
Run Code Online (Sandbox Code Playgroud)
所以我们需要稍微修改价格方法来处理这种情况.基本上我们检查是否有价格规则,否则我们返回0.
around 'price' => sub {
my ( $next, $self, $item ) = @_;
return 0 unless $self->has_price($item);
$self->$next($item);
};
Run Code Online (Sandbox Code Playgroud)
接下来,我们需要跟踪扫描的项目,以便我们可以构建总计.
has items => (
isa => 'ArrayRef',
traits => ['Array'],
default => sub { [] },
handles => {
scan => 'push',
items => 'elements'
},
);
Run Code Online (Sandbox Code Playgroud)
"Native Attributes"委托再次提供scan了Kata中的测试正在寻找的方法.
# Translated to Test::More
my $co = Checkout->new( pricing_rules => $RULES );
is( $co->total, 0 );
$co->scan("A");
is( $co->total, 50 );
Run Code Online (Sandbox Code Playgroud)
最后一个total方法使用函数是微不足道List::Util的sum.
sub total {
my ($self) = @_;
my @prices = map { $self->price($_) } $self->items;
return sum( 0, @prices );
}
Run Code Online (Sandbox Code Playgroud)
这段代码没有完全实现Kata的解决方案,但它确实提供了一个更好的问题状态模型,并展示了一个更"Moosey"的解决方案.
完整代码和翻译测试如下所示.
{
package Checkout;
use Moose;
our $VERSION = '0.01';
use namespace::autoclean;
use List::Util qw(sum);
has pricing_rules => (
isa => 'HashRef',
is => 'ro',
traits => ['Hash'],
default => sub { {} },
handles => {
price => 'get',
has_price => 'exists'
},
);
around 'price' => sub {
my ( $next, $self, $item ) = @_;
return 0 unless $self->has_price($item);
$self->$next($item);
};
has items => (
isa => 'ArrayRef',
traits => ['Array'],
default => sub { [] },
handles => {
scan => 'push',
items => 'elements'
},
);
sub total {
my ($self) = @_;
my @prices = map { $self->price($_) } $self->items;
return sum( 0, @prices );
}
__PACKAGE__->meta->make_immutable;
}
{
package main;
use 5.10.0;
use Test::More;
our $RULES = { A => 50 }; # need a full ruleset
sub price {
my ($goods) = @_;
my $co = Checkout->new( pricing_rules => $RULES ); # use BUILDARGS the example API
for ( split //, $goods ) { $co->scan($_) }
return $co->total;
}
TODO: {
local $TODO = 'Kata 9 not implemented';
is( price(""), 0 );
is( price("A"), 50 );
is( price("AB"), 80 );
is( price("CDBA"), 115 );
is( price("AA"), 100 );
is( price("AAA"), 130 );
is( price("AAAA"), 180 );
is( price("AAAAA"), 230 );
is( price("AAAAAA"), 260 );
is( price("AAAB"), 160 );
is( price("AAABB"), 175 );
is( price("AAABBD"), 190 );
is( price("DABABA"), 190 );
my $co = Checkout->new( pricing_rules => $RULES );
is( $co->total, 0 );
$co->scan("A");
is( $co->total, 50 );
$co->scan("B");
is( $co->total, 80 );
$co->scan("A");
is( $co->total, 130 );
$co->scan("A");
is( $co->total, 160 );
$co->scan("B");
is( $co->total, 175 );
}
done_testing();
}
Run Code Online (Sandbox Code Playgroud)