pythonによるデザインパターン[Strategy]
# Intro
この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。
筆者の環境は以下の通りです。Python 3.6.3
まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「Strategy」です。
今回は細かい部分は割と端折って、大きな流れを書いています。
# Explanation
本書のp136によると、 Strategyとは
アルゴリズム(戦略・作戦・方策)をカチッと切り替え、同じ問題を別の方法で解くのを容易にするパターン
というものです。例えば、ある機能をアルゴリズムAで実装していて、突然アルゴリズムBで試したくなった時に、すぐにアルゴリズムを変更できるような設計にします。ABテストなどと相性が良さそうですね。
# Practice
## Specification
TaroとHanaがじゃんけんをするプログラムを作ってください。そのじゃんけんを10000回した結果を以下のように表示してください。
Total Result
[Taro: 10000games, 3122win, 3463lose]
[Hana: 10000games, 3463win, 3122lose]
但し、Taroは勝ったら次も同じ手を出す戦略(WinningStrategy
)をとり、Hanaは1回前の手から次の手を確率的に計算する戦略(ProbStrategy
)を取ってください。
## Explanation
まずは手を表す class Hand
を作成しましょう。
グー、チョキ、パーを表す数字(HANDVALUE
)を定義し、hands
という変数にグー、チョキー、パーのそれぞれに対する3つのclass Hand
のインスタンスの配列を作成します。
def get_hand(self,hand)
ではグー、チョキ、パーを表す数字(HANDVALUE
)を引数にとり、hands
という変数からそれに対応したclass Hand
のインスタンスを取り出します。この辺りはSingletonのようなことをしています(今回はグー、チョキー、パーの3つのインスタンスしか作らないのでTripleton)。Singletonについては以下で解説しています。
あとはdef is_stronger_than
,def is_weaker_than
,def fight
などで、じゃんけんの勝敗を判定しています。
次に戦略を表すclassを考えましょう。まずは戦略自体を表す抽象クラスとして、class Strategy
を作成しましょう。
class Strategy(metaclass=ABCMeta):
@abstractmethod
def next_hand(self):
pass @abstractmethod
def study(self, win):
pass
次の手を返す関数( def next_hand
)と戦略を学習する関数(def study
)を用意します。
次に具体的な戦略を実装します。class Strategy
を継承して、Taroの勝ったら次も同じ手を出す戦略はclass WinningStrategy
を作成し、Hanaの1回前の手から次の手を確率的に計算する戦略はclass ProbStrategy
を作成します。
それぞれ難しいそうなことをやっていますが、まあそこはあまり重要ではないです。ざっくりというと、class WinningStrategy
は won
という変数に前の勝敗を代入しておいて、それによってnext_handを変える、class ProbStrategy
はhistory
という変数に過去の勝ち負けを登録しておいて、そこからnext_handを変えるといった感じです。
ここで重要なのは、戦略(class Strategy
)は手を表すclass Hand
や、プレーヤーを表すclass Player
のことを一切考えないでよい点です。これによって、新しい戦略を追加するときも、class Strategy
を継承しさえすれば、他の状態を一切考えず、ただ新しいクラスを追加すれば良いだけです。
最後にプレイヤーを表すクラスclass Player
を作成しましょう。
初めにプレーヤ名 name
、戦略 strategy
、負けた数 losecount
、勝った数 wincount
、じゃんけんをした数 gamecount
を代入します。最も重要なのは、プレーヤーのattributeとして strategy
を持たせていることです(委譲)。これによって、戦略を変えたいときは strategy
に別の戦略を表すクラスを代入すれば良いだけで、とても簡単です。
def next_hand
では、 strategy
を通して次の手を返します。 def win
def lose
def lose
では、勝敗の数をカウントします。このとき strategy
の def study
を実行することで、各戦略に応じた学習を行います。 def to_string
は最後に表示をする時にformatを整えているだけです。
## Sample Code
上で挙げたものをまとめたもので、内容は同じです。
# Conclusion
繰り返しになるのですが、重要なのは委譲によってclass Player
に strategy
を持たせている点です。よって、プレーヤーのインスタンスを作る時は、以下のように戦略を示すクラスを引数で渡しましょう。
player1 = Player('Taro', WinningStrategy())
player2 = Player('Hana', ProbStrategy())
この生成する時の違いだけで、あとは何も意識せずコードを書けば戦略が変わります。だから、例えばTaroとHanaの戦略を同じにしたいと思えば全体のコードのうち下の部分だけ変えるだけいいのです。
player1 = Player('Taro', ProbStrategy())
player2 = Player('Hana', ProbStrategy())
便利ですね。
このデザインパターンを初めて知った時に、あまりの構成の綺麗さに感動しました笑。すぐ実践できそうなデザインパターンなので、積極的に使っていきたいです。
# ref
- 「増補改訂版 Java言語で学ぶデザインパターン入門」 結城浩(著)第11版