GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Strategyパターン(ストラテジー・パターン)
Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。
Strategyパターンはアルゴリズムを記述するサブルーチンへの参照をデータ構造の内部に保持する。このパターンの実現には、関数ポインタや関数オブジェクト、デリゲートのほか、オーソドックスなオブジェクト指向言語におけるポリモーフィズムと委譲、あるいはリフレクションによる動的ダック・タイピングなどが利用される。
UML class and sequence diagram
UML class diagram
□ 備忘録
Strategy
パターンでは、アルゴリズムの部分を他の部分と意識的に分離して、そのアルゴリズムとのインタフェース部分だけを規定します。そして、プログラムから委譲によってアルゴリズムを利用する形態になります。
プログラムを改良する場合、Strategy
パターンを使っていれば、Strategy
役のインタフェースを変更しないように注意して、ConcreteStrategy
役だけを修正すればよく、さらに、委譲という緩やかな結びつきを使っているから、アルゴリズムを容易に切り替えることも可能になります。
■ "Strategy"のサンプルプログラム
実際に、Strategyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、TaroとHanakoが、ジャンケンを繰り返して勝敗を競うものになります。
<ジャンケンの戦略>
- Taroは、ジャンケンに勝ったら、次のジャンケンでも同じ手で挑む
- Hanakoは、"グー", "チョキ", "パー"の順番にジャンケンに挑む
$ python Main.py
Even...
Winner:[Hana: 1games, 0win, 0lose]
Winner:[Hana: 2games, 1win, 0lose]
Winner:[Hana: 3games, 2win, 0lose]
Even...
Winner:[Taro: 5games, 0win, 3lose]
Winner:[Hana: 6games, 3win, 1lose]
Winner:[Taro: 7games, 1win, 4lose]
Winner:[Hana: 8games, 4win, 2lose]
Even...
...(snip)
Winner:[Taro: 9976games, 2433win, 5070lose]
Winner:[Hana: 9977games, 5070win, 2434lose]
Winner:[Hana: 9978games, 5071win, 2434lose]
Winner:[Taro: 9979games, 2434win, 5072lose]
Winner:[Hana: 9980games, 5072win, 2435lose]
Even...
Winner:[Hana: 9982games, 5073win, 2435lose]
Even...
Even...
Winner:[Taro: 9985games, 2435win, 5074lose]
Winner:[Hana: 9986games, 5074win, 2436lose]
Winner:[Taro: 9987games, 2436win, 5075lose]
Winner:[Hana: 9988games, 5075win, 2437lose]
Winner:[Taro: 9989games, 2437win, 5076lose]
Winner:[Hana: 9990games, 5076win, 2438lose]
Winner:[Hana: 9991games, 5077win, 2438lose]
Winner:[Taro: 9992games, 2438win, 5078lose]
Winner:[Hana: 9993games, 5078win, 2439lose]
Winner:[Taro: 9994games, 2439win, 5079lose]
Winner:[Hana: 9995games, 5079win, 2440lose]
Winner:[Hana: 9996games, 5080win, 2440lose]
Winner:[Hana: 9997games, 5081win, 2440lose]
Winner:[Hana: 9998games, 5082win, 2440lose]
Even...
Total Result:
[Taro: 10000games, 2440win, 5083lose]
[Hana: 10000games, 5083win, 2440lose]
結果は、hanakoのジャンケン戦略("グー", "チョキ", "パー"の順番にジャンケンに挑む)の方が、Taroのジャンケン戦略よりも優れている結果になりました。これは、Taroのジャンケン戦略(ジャンケンに勝ったら、次のジャンケンでも同じ手で挑む)では、絶対に連勝できないためです。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Strategy
- ディレクトリ構成
.
├── Main.py
└── strategy
├── __init__.py
├── hand.py
├── player.py
└── strategy.py
(1) Strategy(戦略)の役
戦略を利用するためのインタフェースを定める役です。
サンプルプログラムでは、Strategy
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class Strategy(metaclass=ABCMeta):
@abstractmethod
def nextHand(self):
pass
@abstractmethod
def study(self, win):
pass
(2) ConcreteStrategy(具体的戦略)の役
Strategy
役のインタフェースを実際に実装する役です。ここで具体的な戦略(作業・方策・方法・アルゴリズム)を実際にプログラミングします。
サンプルプログラムでは、WinningStrategy
クラスと、CircularStrategy
クラスが、この役を努めます。
import random
from strategy.hand import Hand
class WinningStrategy(Strategy):
def __init__(self):
self.__won = False
self.__prevHand = None
def nextHand(self):
if not self.__won:
self.__prevHand = Hand.getHand(random.randint(0, 2))
return self.__prevHand
def study(self, win):
self.__won = win
class CircularStrategy(Strategy):
def __init__(self):
self.__Hand = 0
def nextHand(self):
return Hand.getHand(self.__Hand)
def study(self, win):
self.__Hand = (self.__Hand + 1) % 3
(3) Context(文脈)の役
Strategy
役を利用する役です。ConcreteStrategy
役のインスタンスを持っていて、必要に応じてそれを利用します。
サンプルプログラムでは、Player
クラスが、この役を努めます。
class Player(object):
def __init__(self, name, strategy):
self.__name = name
self.__strategy = strategy
self.__wincount = 0
self.__losecount = 0
self.__gamecount = 0
def nextHand(self):
return self.__strategy.nextHand()
def win(self):
self.__strategy.study(True)
self.__wincount += 1
self.__gamecount += 1
def lose(self):
self.__strategy.study(False)
self.__losecount += 1
self.__gamecount += 1
def even(self):
self.__gamecount += 1
def __str__(self):
return "[{0}: {1}games, {2}win, {3}lose]".format(self.__name,
self.__gamecount,
self.__wincount,
self.__losecount)
(4) Client(依頼人)の役
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
import sys
import strategy.strategy
from strategy.strategy import WinningStrategy, CircularStrategy
from strategy.player import Player
def startMain():
player1 = Player("Taro", WinningStrategy())
player2 = Player("Hana", CircularStrategy())
for _ in range(10000):
nextHand1 = player1.nextHand()
nextHand2 = player2.nextHand()
if nextHand1.isStrongerThan(nextHand2):
print("Winner:{0}".format(player1))
player1.win()
player2.lose()
elif nextHand2.isStrongerThan(nextHand1):
print("Winner:{0}".format(player2))
player1.lose()
player2.win()
else:
print("Even...")
player1.even()
player2.even()
print("Total Result:")
print(player1)
print(player2)
if __name__ == '__main__':
startMain()
(5) その他
ジャンケンの勝敗を管理します。
class Hand(object):
HANDVALUE_GUU = 0
HANDVALUE_CHO = 1
HANDVALUE_PAA = 2
name = ["グー", "チョキ", "パー"]
hands = []
def __init__(self, handvalue):
self.__handvalue = handvalue
@classmethod
def getHand(cls, handvalue):
return cls.hands[handvalue]
def isStrongerThan(self, hand):
return self.fight(hand) == 1
def isWeakerThan(self, hand):
return self.fight(hand) == -1
def fight(self, hand):
if self == hand:
return 0
elif (self.__handvalue + 1) % 3 == hand.__handvalue:
return 1
else:
return -1
# def toString(self):
# return self.name[self.__handvalue]
Hand.hands.append(Hand(Hand.HANDVALUE_GUU))
Hand.hands.append(Hand(Hand.HANDVALUE_CHO))
Hand.hands.append(Hand(Hand.HANDVALUE_PAA))