Как я могу сделать мой метод Perl более похожим на Moose? - PullRequest
3 голосов
/ 26 октября 2010

Я практикую Kata Nine: вернемся к CheckOut в Perl, одновременно пытаясь использовать 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;

Метод price в целом не очень приятен, и я чувствую, что это может быть подвергнуто дальнейшему рефакторингу.

Кто-нибудь знает, как это можно улучшить?

Ответы [ 2 ]

8 голосов
/ 27 октября 2010

Первое, что я заметил, это то, что вы не используете явный атрибут для вашего класса.

$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::Utilsum 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();
}
2 голосов
/ 26 октября 2010

use strict; и use warnings; можно удалить, так как use Moose; уже сделает это за вас.Вы также можете использовать Moose для создания атрибута только для чтения вместо использования модуля Readonly.

package Checkout;

# $Id$                                                                                                                                                                        
#                                                                                                                                                                             

our $VERSION = '0.01';

use Moose;

has '_prices' => (
  is => 'ro',
  isa => 'HashRef',
  lazy_build => 1,
  init_arg => undef, # do not allow in constructor                                                                                                                            
);

sub _build__prices {
    my ( $self ) = @_;

    return {
            'A' => 50
           };
}

sub price {
    my ( $self, $items ) = @_;

    return (exists $self->_prices->{$items} ? $self->_prices->{$items} : 0);
}

__PACKAGE__->meta->make_immutable;

no Moose;

1;
...