如何使我的 Perl 方法更像 Moose?

发布于 2024-09-28 22:34:05 字数 721 浏览 4 评论 0原文

我正在 Perl 中练习 Kata Nine: Back to the CheckOut 同时也尝试使用第一次见到驼鹿。

到目前为止,我已经创建了以下类:

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 方法感觉不太像 Moose,我觉得这可以进一步重构。

有人对如何改进有任何意见吗?

I am practising Kata Nine: Back to the CheckOut in Perl whilst also trying to use Moose for the first time.

So far, I've created the following class:

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;

The whole price method doesn't feel very Moose-ish and I feel like this could be refactored further.

Does anyone have any input on how this could be improved?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

很快妥协 2024-10-05 22:34:05

我注意到的第一件事是您没有为您的
班级。

$self->{price} = 0;

这违反了使用 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) }
    }
}

然而,您提供的代码的主要问题是您没有
真正对 Kata 描述的问题进行建模。首先是卡塔
指出您需要在每次调用时传递定价规则
结帐对象。所以我们知道我们需要将这个状态保存在其他东西中
比一系列 ReadOnly 类变量。

has pricing_rules => (
    isa     => 'HashRef',
    is      => 'ro',
    traits  => ['Hash'],
    default => sub { {} },
    handles => {
        price     => 'get',
        has_price => 'exists'
    },
);

您会看到这里的价格方法现在是使用 Moose 的“Native
属性”委托。这将在项目和规则之间执行查找。
然而,这不会处理对不存在的元素进行查找的情况
存在。展望一下 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'
    },
);

同样,“本机属性”委托提供了 scan 方法,
测试中的Kata正在寻找。

# Translated to Test::More
my $co = Checkout->new( pricing_rules => $RULES );
is( $co->total, 0 );
$co->scan("A");
is( $co->total, 50 );

最后,使用 List::Utilsum 函数可以轻松实现 total 方法。

sub total {
    my ($self) = @_;
    my @prices = map { $self->price($_) } $self->items;
    return sum( 0, @prices );
}

这段代码并没有完全实现 Kata 的解决方案,但它确实实现了
提出问题状态的更好模型并显示更多
“穆西”解决方案。

完整的代码和翻译的测试如下所示。

{

    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();
}

First thing I noticed is that you're not using an explicit attribute for your
class.

$self->{price} = 0;

This violates most of the encapsulation that using Moose provides. A Moose solution
would at the least make price into an actual attribute.

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) }
    }
}

However the main problem with the code you've presented is that you're not
really modeling the problem described by the Kata. First the Kata specifically
states that you'll need to pass in the pricing rules on each invocation of the
Checkout Object. So we know we'll need to save this state in something other
than a series of ReadOnly class variables.

has pricing_rules => (
    isa     => 'HashRef',
    is      => 'ro',
    traits  => ['Hash'],
    default => sub { {} },
    handles => {
        price     => 'get',
        has_price => 'exists'
    },
);

You'll see the price method here is now a delegate using Moose's "Native
Attributes" delegation. This will perform a lookup between an Item and a Rule.
This however won't handle the case of a lookup on a element that doesn't
exist. Peeking ahead one of the unit tests the Kata provides is exactly that
kind of a lookup:

is( price(""),     0 ); # translated to Test::More

So we'll need to modify the price method slightly to handle that case.
Basically we check to see if we have a price rule, otherwise we return 0.

around 'price' => sub {
    my ( $next, $self, $item ) = @_;
    return 0 unless $self->has_price($item);
    $self->$next($item);
};

Next we'll need to track the items as they're scanned so we can build a total.

has items => (
    isa     => 'ArrayRef',
    traits  => ['Array'],
    default => sub { [] },
    handles => {
        scan  => 'push',
        items => 'elements'
    },
);

Again the "Native Attributes" delegation provides the scan method that the
test in the Kata are looking for.

# Translated to Test::More
my $co = Checkout->new( pricing_rules => $RULES );
is( $co->total, 0 );
$co->scan("A");
is( $co->total, 50 );

Finally a total method is trivial using List::Util's sum function.

sub total {
    my ($self) = @_;
    my @prices = map { $self->price($_) } $self->items;
    return sum( 0, @prices );
}

This code doesn't fully implement the solution to the Kata, but it does
present a much better model of the problem state and show's a much more
"Moosey" solution.

The full code and translated tests are presented below.

{

    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();
}
表情可笑 2024-10-05 22:34:05

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;

The use strict; and use warnings; can be removed as use Moose; will already do that for you. You can also use Moose to create read-only attribute instead of using Readonly module.

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文