Первое, что я заметил, это то, что вы не используете явный атрибут для вашего класса.
$self->{price} = 0;
Это нарушает большую часть инкапсуляции, которую обеспечивает 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) }
}
}
Однако основная проблема с кодом, который вы представили, заключается в том, что вы на самом деле не моделируетепроблема описана в ката.Во-первых, в Ката конкретно указано, что вам нужно будет указывать правила ценообразования при каждом вызове объекта Checkout.Таким образом, мы знаем, что нам нужно сохранить это состояние в нечто отличное от ряда переменных класса ReadOnly.
has pricing_rules => (
isa => 'HashRef',
is => 'ro',
traits => ['Hash'],
default => sub { {} },
handles => {
price => 'get',
has_price => 'exists'
},
);
Теперь вы увидите, что метод price теперь является делегатом, использующим делегирование Moose «Native Attributes»,Это выполнит поиск между Предметом и Правилом.Это однако не будет обрабатывать случай поиска на элементе, который не существует.Забегая вперед, один из модульных тестов, которые предоставляет Kata, является именно таким поиском:
is( price(""), 0 ); # translated to Test::More
Так что нам нужно немного изменить метод цен, чтобы справиться с этим случаем.По сути, мы проверяем, есть ли у нас правило цены, в противном случае мы возвращаем 0.
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'
},
);
Опять делегирование "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 );
Наконец, метод total
тривиален с использованием List::Util
sum
function.
sub total {
my ($self) = @_;
my @prices = map { $self->price($_) } $self->items;
return sum( 0, @prices );
}
Этот код не полностью реализует решение 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();
}