sironekotoroの日記

Perl で楽をしたい

「良いコード/悪いコードで学ぶ設計入門」第6章その2 ポリシーパターン #ミノ駆動本

リスト6.41 と 6.42 Gold会員とシルバー会員の判定メソッド

あー、こういうコード書いた覚えあります・・・

ゴールド会員の判定ロジック

    if ( 100000 <= $history->total_amount ) {
        if ( 10 <= $history->purchase_frequency_per_month ) {
            if ( $history->return_rate <= 0.001 ) {
                return !!1;
            }
        }
    }

シルバー会員の判定ロジック

    if ( 10 <= $history->purchase_frequency_per_month ) {
        if ( $history->return_rate <= 0.001 ) {
            return !!1;
        }
    }

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

use Function::Parameters;
use Types::Standard -types;

package History {
    use Carp qw/croak/;
    use Mouse;
    use Readonly;
    use constant { MIN => 0 };

    has total_amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has purchase_frequency_per_month => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has return_rate => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    __PACKAGE__->meta->make_immutable();
}

fun is_gold_customer($history) {

    if ( 100000 <= $history->total_amount ) {
        if ( 10 <= $history->purchase_frequency_per_month ) {
            if ( $history->return_rate <= 0.001 ) {
                return !!1;
            }
        }
    }
    return "!!0";
}

fun is_silver_customer($history) {

    if ( 10 <= $history->purchase_frequency_per_month ) {
        if ( $history->return_rate <= 0.001 ) {
            return !!1;
        }
    }
    return "!!0";
}

package main;

my $history = History->new(
    total_amount                 => 200000,
    purchase_frequency_per_month => 20,
    return_rate                  => 0.0000,
);

if ( is_gold_customer($history) ) {
    say "GOLD customer!";
}

if ( is_silver_customer($history) ) {
    say "SILVER customer!";
}

このような、判定ロジックを再利用するために、ポリシーパターンを使う、と。

リスト 6.43

前の節の魔法のところと似ているところもあり、サラッとかけた。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

use Function::Parameters;

package History {
    use Carp qw/croak/;
    use Mouse;
    use Readonly;
    use constant { MIN => 0 };

    has total_amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has purchase_frequency_per_month => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has return_rate => (
        is       => 'ro',
        isa      => 'Num',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    __PACKAGE__->meta->make_immutable();
}

# 優良顧客のルールを再現するinterface
package ExcelentCustomerRule {
    use Mouse::Role;
    requires 'ok';

    method ok($history) { }
}

package GoldCustomerPurchaseAmontRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return 100000 <= $history->total_amount;
    }
}

package PurchaseFrequencyRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return 10 <= $history->purchase_frequency_per_month;
    }
}

package ReturnRateRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return $history->return_rate <= 0.001;
    }
}

package ExcelentCustomerPolicy {
    use Mouse;

    has rules => (
        is       => 'rw',
        isa      => 'ArrayRef[Object]',
        required => 0,
    );

    method add($rule) {
        my $rules = $self->rules();
        push @{$rules}, $rule;
        $self->rules($rules);
    }

    method comply_with_all($history) {
        for my $rule ( @{ $self->rules } ) {
            if ( !$rule->ok($history) ) {
                return !!0;
            }
        }
        return !!1;
    }
}

package main;

my $history = History->new(
    total_amount                 => 200000,
    purchase_frequency_per_month => 20,
    return_rate                  => 0.001,
);

# 特別会員のポリシー初期化
my $excelent_customer_policy = ExcelentCustomerPolicy->new();

# 特別会員の条件を追加していく
$excelent_customer_policy->add( GoldCustomerPurchaseAmontRule->new() );
$excelent_customer_policy->add( PurchaseFrequencyRule->new() );
$excelent_customer_policy->add( ReturnRateRule->new() );

# 特別会員稼働かを判定
say $excelent_customer_policy->comply_with_all($history);

リスト6.49, 6.50

いまは、ルールを外から組み込めるようにしてある。

これを「ゴールド会員専用」のポリシークラスにする。うんうん、わかる。

「シルバー会員専用」のポリシークラスまで作ったやつで終わろう。

前回の魔法のやつは最後までいまいち理解してない感に付き纏われたけど、今回のポリシーパターンはなんかすごい腑に落ちた感がある。

この違いはなんなのだろうなぁ。

ここクリックして展開

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

use Function::Parameters;

package History {
    use Carp qw/croak/;
    use Mouse;
    use Readonly;
    use constant { MIN => 0 };

    has total_amount => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has purchase_frequency_per_month => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    has return_rate => (
        is       => 'ro',
        isa      => 'Num',
        required => 1,
        trigger  => sub { croak '', unless ( $_[0] ) },
    );
    __PACKAGE__->meta->make_immutable();
}

# 優良顧客のルールを再現するinterface
package ExcelentCustomerRule {
    use Mouse::Role;
    requires 'ok';

    method ok($history) { }
}

package GoldCustomerPurchaseAmontRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return 100000 <= $history->total_amount;
    }
}

package PurchaseFrequencyRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return 10 <= $history->purchase_frequency_per_month;
    }
}

package ReturnRateRule {
    use Carp qw/croak/;
    use Mouse;
    with 'ExcelentCustomerRule';

    method ok($history) {
        return $history->return_rate <= 0.001;
    }
}

package GoldCustomerPolicy {
    use Mouse;

    has rules => (
        is       => 'rw',
        isa      => 'ArrayRef[Object]',
        required => 0,
        default  => sub {
            [
                GoldCustomerPurchaseAmontRule->new(),
                PurchaseFrequencyRule->new(),
                ReturnRateRule->new(),
            ]
        }
    );

    method comply_with_all($history) {
        for my $rule ( @{ $self->rules } ) {
            if ( !$rule->ok($history) ) {
                return !!0;
            }
        }
        return !!1;
    }
}

package SilverCustomerPolicy {
    use Mouse;

    has rules => (
        is       => 'rw',
        isa      => 'ArrayRef[Object]',
        required => 0,
        default  => sub {
            [ PurchaseFrequencyRule->new(), ReturnRateRule->new(), ]
        }
    );

    method comply_with_all($history) {
        for my $rule ( @{ $self->rules } ) {
            if ( !$rule->ok($history) ) {
                return !!0;
            }
        }
        return !!1;
    }
}

package main;

# 模範的ゴールド会員
my $gold_customer_history = History->new(
    total_amount                 => 200000,
    purchase_frequency_per_month => 20,
    return_rate                  => 0.001,
);

# ゴールド会員のポリシー
my $gold_customer_policy = GoldCustomerPolicy->new();

# 特別会員かどうかを判定
if ( $gold_customer_policy->comply_with_all($gold_customer_history) ) {
    say "Gold 会員です";
}

# 模範的シルバー会員
my $silver_customer_history = History->new(
    total_amount                 => 100000,
    purchase_frequency_per_month => 20,
    return_rate                  => 0.001,
);

# シルバー会員のポリシー
my $silver_customer_policy = SilverCustomerPolicy->new();

# シルバー会員かどうかを判定
if ( $silver_customer_policy->comply_with_all($silver_customer_history) ) {
    say "Silver 会員です";
}