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 WinningStrategywon という変数に前の勝敗を代入しておいて、それによってnext_handを変える、class ProbStrategyhistory という変数に過去の勝ち負けを登録しておいて、そこから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 では、勝敗の数をカウントします。このとき strategydef study を実行することで、各戦略に応じた学習を行います。 def to_string は最後に表示をする時にformatを整えているだけです。

## Sample Code

上で挙げたものをまとめたもので、内容は同じです。

# Conclusion

繰り返しになるのですが、重要なのは委譲によってclass Playerstrategy を持たせている点です。よって、プレーヤーのインスタンスを作る時は、以下のように戦略を示すクラスを引数で渡しましょう。

player1 = Player('Taro', WinningStrategy())
player2 = Player('Hana', ProbStrategy())

この生成する時の違いだけで、あとは何も意識せずコードを書けば戦略が変わります。だから、例えばTaroとHanaの戦略を同じにしたいと思えば全体のコードのうち下の部分だけ変えるだけいいのです。

player1 = Player('Taro', ProbStrategy())
player2 = Player('Hana', ProbStrategy())

便利ですね。

このデザインパターンを初めて知った時に、あまりの構成の綺麗さに感動しました笑。すぐ実践できそうなデザインパターンなので、積極的に使っていきたいです。

# ref

--

--