Strategyパターン
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
サンプルは、じゃんけんの手を出す戦略をstrategyパターンで書いている。
まずHandクラス。
package Hand; use Moose; use MooseX::ClassAttribute; use Jcode; use constant { HANDVALUE_GUU => 0, HANDVALUE_CHO => 1, HANDVALUE_PAA => 2, }; class_has hand => ( is => 'rw', isa => 'ArrayRef', default => sub { return [ Hand->new( handvalue => HANDVALUE_GUU ), Hand->new( handvalue => HANDVALUE_CHO ), Hand->new( handvalue => HANDVALUE_PAA ), ]; } ); has name => ( is => 'ro', isa => 'ArrayRef', default => sub { return [ 'グー', 'チョキ', 'パー', ]; } ); has handvalue => ( is => 'rw', isa => 'Int', required => 1, ); sub getHand { my $handvalue = shift; return __PACKAGE__->hand->[$handvalue]; } sub isStrongerThan { my $self = shift; my $h = shift; return ($self->fight($h) == 1); } sub isWeakerThan { my $self = shift; my $h = shift; return ($self->fight($h) == -1); } sub fight { my $self = shift; my $h = shift; if ($self->{handvalue} == $h->{handvalue}) { return 0; } elsif (($self->{handvalue} + 1) % 3 == $h->{handvalue}) { return 1; } else { return -1; } } sub toString { my $self = shift; return jcode($self->name->[$self->{handvalue}])->euc; } 1;
ここでは「MooseX::ClassAttribute」を使って、下記のようにしてクラス変数を定義した。
class_has hand => ( is => 'rw', isa => 'ArrayRef', default => sub { return [ Hand->new( handvalue => HANDVALUE_GUU ), Hand->new( handvalue => HANDVALUE_CHO ), Hand->new( handvalue => HANDVALUE_PAA ), ]; } );
次にStrategyインタフェースを定義し、これを継承した「WinningStrategy」と「ProbStrategy」を定義する。
package Strategy; use Moose::Role; use Hand; requires qw/nextHand study/; package WinningStrategy; use Moose; use Hand; with 'Strategy'; has won => ( is => 'rw', isa => 'Int', default => 0, ); has prevHand => ( is => 'rw', isa => 'Object', ); sub nextHand { my $self = shift; if (!$self->{won}) { my $rand = int(rand 3); $self->{prevHand} = Hand::getHand($rand); } return $self->{prevHand}; } sub study { my $self = shift; my $win = shift; $self->{won} = $win; } package ProbStrategy; use Moose; use Hand; with Strategy; has prevHandValue => ( is => 'rw', isa => 'Int', default => 0, ); has currentHandValue => ( is => 'rw', isa => 'Int', default => 0, ); has history => ( is => 'rw', isa => 'ArrayRef', default => sub { return [ [ 1, 1, 1, ], [ 1, 1, 1, ], [ 1, 1, 1, ], ]; }, ); sub nextHand { my $self = shift; my $bet = rand getSum($self,$self->currentHandValue); my $handvalue = 0; if ($bet < $self->history->[$self->currentHandValue]->[0]) { $handvalue = 0; } elsif ($bet < $self->history->[$self->currentHandValue]->[0] + $self->history->[$self->currentHandValue]->[1]) { $handvalue = 1; } else { $handvalue = 2; } $self->{prevHandValue} = $self->{currentHandValue}; $self->{currentHandValue} = $handvalue; return Hand::getHand($handvalue); } sub getSum { my $self = shift; my $hv = shift; my $sum = 0; for (my $i = 0; $i < 3; $i++) { $sum += $self->history->[$hv]->[$i]; } return $sum; } sub study { my $self = shift; my $win = shift; if ($win) { $self->history->[$self->prevHandValue]->[$self->currentHandValue]++; } else { $self->history->[$self->prevHandValue]->[($self->currentHandValue + 1) % 3]++; $self->history->[$self->prevHandValue]->[($self->currentHandValue + 2) % 3]++; } } 1;
WinningStrategyは、「勝ったときに出した手を、次にまた出す。勝てなかったら、次はランダムに手を出す」といった戦略になっている。
ProbStrategyは、「過去の勝ち負けの履歴を使って、それぞれの手を出す確率を変える」といった戦略になっている。
次にPlayerクラス。
package Player; use Moose; use Strategy; has name => ( is => 'rw', isa => 'Str', required => 1, ); has strategy => ( is => 'rw', isa => 'Object', required => 1, ); has wincount => ( is => 'rw', isa => 'Int', default => 0, ); has losecount => ( is => 'rw', isa => 'Int', default => 0, ); has gamecount => ( is => 'rw', isa => 'Int', default => 0, ); sub nextHand { my $self = shift; return $self->strategy->nextHand(); } sub win { my $self = shift; $self->strategy->study(1); $self->{wincount}++; $self->{gamecount}++; } sub lose { my $self = shift; $self->strategy->study(0); $self->{losecount}++; $self->{gamecount}++; } sub even { my $self = shift; $self->{gamecount}++; } sub toString { my $self = shift; return '[' . $self->{name} . ':' . $self->{gamecount} . 'games, ' . $self->{wincount} . ' win, ' . $self->{losecount} . ' lose' . ']'; } 1;
Playerは、名前と戦略が渡されてインスタンスが生成される。このインスタンス生成時に戦略が切り替えられる。
以下が、実行プログラムと実行結果。
#!/usr/bin/perl use strict; use warnings; use Player; use Strategy; my $player1 = Player->new( name => 'Taro', strategy => WinningStrategy->new); my $player2 = Player->new( name => 'Hana', strategy => ProbStrategy->new); for (my $i = 0; $i < 10000; $i++) { my $nextHand1 = $player1->nextHand(); my $nextHand2 = $player2->nextHand(); if ($nextHand1->isStrongerThan($nextHand2)) { print 'Winner:' . $player1->name . "\n"; $player1->win(); $player2->lose(); } elsif ($nextHand2->isStrongerThan($nextHand1)) { print 'Winner:' . $player2->name . "\n"; $player1->lose(); $player2->win(); } else { print "Even...\n"; $player1->even(); $player2->even(); } print "Total result:\n"; print $player1->toString() . "\n"; print $player2->toString() . "\n"; } exit;
〜略〜
Total result:
[Taro:9996games, 3091 win, 3494 lose]
[Hana:9996games, 3494 win, 3091 lose]
Winner:Hana
Total result:
[Taro:9997games, 3091 win, 3495 lose]
[Hana:9997games, 3495 win, 3091 lose]
Winner:Hana
Total result:
[Taro:9998games, 3091 win, 3496 lose]
[Hana:9998games, 3496 win, 3091 lose]
Winner:Hana
Total result:
[Taro:9999games, 3091 win, 3497 lose]
[Hana:9999games, 3497 win, 3091 lose]
Even...
Total result:
[Taro:10000games, 3091 win, 3497 lose]
[Hana:10000games, 3497 win, 3091 lose]
デザインパターンの概要
戦略ごとに「WinningStrategy」と「ProbStrategy」が定義された。これらのクラスは「Strategy」クラスを継承することで、インターフェースを統一された。例では「nextHand()」と「study()」がインタフェースだった。戦略クラスを利用する「Player」クラスは、
$strategy->nextHand(); $strategy->study();
で呼び出している。ここで「$strategy」に、「WinningStrategy」のインスタンスか「ProbStrategy」のインスタンスを入れることによって、じゃんけんの戦略が切り替えられるようになっている。
参考文献
- 結城浩 "増強改訂版 Java言語で学ぶ デザインパターン入門"
- 伊藤直也 "Recent Perl World: 第14回「Hi, Moose!! モダンなオブジェクトシステムを実現する」" Web+DB press vol.45
- hakobe "初めてのMoose" http://www.slideshare.net/hakobe/moose
- "Perlオブジェクト指向プログラミング" http://www.slideshare.net/hakobe/moose
- http://trac.mizzy.org/public/wiki/MooseXClassAttribute