14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonで、デザインパターン「Strategy」を学ぶ

Last updated at Posted at 2020-02-02

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

■ Strategyパターン(ストラテジー・パターン)

Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。
Strategyパターンはアルゴリズムを記述するサブルーチンへの参照をデータ構造の内部に保持する。このパターンの実現には、関数ポインタや関数オブジェクト、デリゲートのほか、オーソドックスなオブジェクト指向言語におけるポリモーフィズムと委譲、あるいはリフレクションによる動的ダック・タイピングなどが利用される。

UML class and sequence diagram

W3sDesign_Strategy_Design_Pattern_UML.jpg

UML class diagram

strategy.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

Strategyパターンでは、アルゴリズムの部分を他の部分と意識的に分離して、そのアルゴリズムとのインタフェース部分だけを規定します。そして、プログラムから委譲によってアルゴリズムを利用する形態になります。
プログラムを改良する場合、Strategyパターンを使っていれば、Strategy役のインタフェースを変更しないように注意して、ConcreteStrategy役だけを修正すればよく、さらに、委譲という緩やかな結びつきを使っているから、アルゴリズムを容易に切り替えることも可能になります

■ "Strategy"のサンプルプログラム

実際に、Strategyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、TaroHanakoが、ジャンケンを繰り返して勝敗を競うものになります。

<ジャンケンの戦略>

  • 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クラスが、この役を努めます。

strategy/strategy.py
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クラスが、この役を努めます。

strategy/strategy.py
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クラスが、この役を努めます。

strategy/player.py
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メソッドが、この役を努めます。

Main.py
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) その他

ジャンケンの勝敗を管理します。

strategy/hand.py
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))

■ 参考URL

14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?