GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Strategy」を学ぶ"
今回は、Pythonで実装した”Strategy”のサンプルアプリをGolangで実装し直してみました。
■ Strategyパターン(ストラテジー・パターン)
Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。
Strategyパターンはアルゴリズムを記述するサブルーチンへの参照をデータ構造の内部に保持する。このパターンの実現には、関数ポインタや関数オブジェクト、デリゲートのほか、オーソドックスなオブジェクト指向言語におけるポリモーフィズムと委譲、あるいはリフレクションによる動的ダック・タイピングなどが利用される。
UML class and sequence diagram
UML class diagram
□ 備忘録
Strategy
パターンでは、アルゴリズムの部分を他の部分と意識的に分離して、そのアルゴリズムとのインタフェース部分だけを規定します。そして、プログラムから委譲によってアルゴリズムを利用する形態になります。
プログラムを改良する場合、Strategy
パターンを使っていれば、Strategy
役のインタフェースを変更しないように注意して、ConcreteStrategy
役だけを修正すればよく、さらに、委譲という緩やかな結びつきを使っているから、アルゴリズムを容易に切り替えることも可能になります。
■ "Strategy"のサンプルプログラム
実際に、Strategyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、TaroとHanakoが、ジャンケンを繰り返して勝敗を競うものになります。
<ジャンケンの戦略>
- Taroは、ジャンケンに勝ったら、次のジャンケンでも同じ手で挑む
- Hanakoは、"グー", "チョキ", "パー"の順番にジャンケンに挑む
$ go run Main.go
Winner:[Hana: 0games, 0win, 0lose]
Winner:[Taro: 1games, 0win, 1lose]
Winner:[Hana: 2games, 1win, 1lose]
Even...
Even...
Winner:[Hana: 5games, 2win, 1lose]
Winner:[Taro: 6games, 1win, 3lose]
Winner:[Hana: 7games, 3win, 2lose]
Winner:[Hana: 8games, 4win, 2lose]
Winner:[Hana: 9games, 5win, 2lose]
Winner:[Taro: 10games, 2win, 6lose]
Winner:[Hana: 11games, 6win, 3lose]
Even...
...(snip)
Winner:[Taro: 9978games, 2446win, 4964lose]
Winner:[Hana: 9979games, 4964win, 2447lose]
Winner:[Taro: 9980games, 2447win, 4965lose]
Winner:[Hana: 9981games, 4965win, 2448lose]
Winner:[Hana: 9982games, 4966win, 2448lose]
Even...
Winner:[Taro: 9984games, 2448win, 4967lose]
Winner:[Hana: 9985games, 4967win, 2449lose]
Winner:[Taro: 9986games, 2449win, 4968lose]
Winner:[Hana: 9987games, 4968win, 2450lose]
Winner:[Taro: 9988games, 2450win, 4969lose]
Winner:[Hana: 9989games, 4969win, 2451lose]
Even...
Winner:[Hana: 9991games, 4970win, 2451lose]
Winner:[Taro: 9992games, 2451win, 4971lose]
Winner:[Hana: 9993games, 4971win, 2452lose]
Winner:[Taro: 9994games, 2452win, 4972lose]
Winner:[Hana: 9995games, 4972win, 2453lose]
Winner:[Taro: 9996games, 2453win, 4973lose]
Winner:[Hana: 9997games, 4973win, 2454lose]
Even...
Winner:[Taro: 9999games, 2454win, 4974lose]
Total Result:
[Taro: 10000games, 2455win, 4974lose]
[Hana: 10000games, 4974win, 2455lose]
結果は、hanakoのジャンケン戦略("グー", "チョキ", "パー"の順番にジャンケンに挑む)の方が、Taroのジャンケン戦略よりも優れている結果になりました。これは、Taroのジャンケン戦略(ジャンケンに勝ったら、次のジャンケンでも同じ手で挑む)では、絶対に連勝できないためです。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Strategy
- ディレクトリ構成
.
├── Main.go
└── strategy
├── hand.go
├── player.go
└── strategy.go
(1) Strategy(戦略)の役
戦略を利用するためのインタフェースを定める役です。
サンプルプログラムでは、Strategy
インタフェースが、この役を努めます。
package strategy
import (
"math/rand"
"time"
)
// Strategy is interface
type Strategy interface {
NextHand() *Hand
study(win bool)
}
(2) ConcreteStrategy(具体的戦略)の役
Strategy
役のインタフェースを実際に実装する役です。ここで具体的な戦略(作業・方策・方法・アルゴリズム)を実際にプログラミングします。
サンプルプログラムでは、WinningStrategy
構造体と、CircularStrategy
構造体が、この役を努めます。
// WinningStrategy is struct
type WinningStrategy struct {
won bool
prevHand *Hand
}
// NewWinningStrategy func for initializing WinningStrategy
func NewWinningStrategy() *WinningStrategy {
return &WinningStrategy{
won: false,
prevHand: nil,
}
}
// NextHand func can handle result of preHand
func (ws *WinningStrategy) NextHand() *Hand {
if !ws.won {
rand.Seed(time.Now().UnixNano())
ws.prevHand = getHand(rand.Intn(3))
}
return ws.prevHand
}
func (ws *WinningStrategy) study(win bool) {
ws.won = win
}
// CircularStrategy is struct
type CircularStrategy struct {
hand int
}
// NewCircularStrategy func for initializing CircularStrategy
func NewCircularStrategy() *CircularStrategy {
return &CircularStrategy{
hand: 0,
}
}
// NextHand func can handle result of each Hand
func (cs *CircularStrategy) NextHand() *Hand {
return getHand(cs.hand)
}
func (cs *CircularStrategy) study(win bool) {
cs.hand = (cs.hand + 1) % 3
}
(3) Context(文脈)の役
Strategy
役を利用する役です。ConcreteStrategy
役のインスタンスを持っていて、必要に応じてそれを利用します。
サンプルプログラムでは、Player
構造体が、この役を努めます。
package strategy
import "fmt"
// Player is struct
type Player struct {
Name string
strategy Strategy
wincount, losecount, gamecount int
}
// NewPlayer func for initializing Player
func NewPlayer(name string, strategy Strategy) *Player {
return &Player{
Name: name,
strategy: strategy,
}
}
// NextHand func can handle result of preHand
func (p *Player) NextHand() *Hand {
return p.strategy.NextHand()
}
// Win func can judge result as game win
func (p *Player) Win() {
p.strategy.study(true)
p.wincount++
p.gamecount++
}
// Lose func can judge result as game lose
func (p *Player) Lose() {
p.strategy.study(false)
p.losecount++
p.gamecount++
}
// Even func can judge result as game even
func (p *Player) Even() {
p.gamecount++
}
// ToString func can display results of all games
func (p *Player) ToString() string {
str := fmt.Sprintf("[%s: %dgames, %dwin, %dlose]", p.Name,
p.gamecount,
p.wincount,
p.losecount)
return str
}
(4) Client(依頼人)の役
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"fmt"
"./strategy"
)
func startMain() {
player1 := strategy.NewPlayer("Taro", strategy.NewWinningStrategy())
player2 := strategy.NewPlayer("Hana", strategy.NewCircularStrategy())
for i := 0; i < 10000; i++ {
hand1 := player1.NextHand()
hand2 := player2.NextHand()
if hand1.IsStrongerThan(hand2) {
fmt.Printf("Winner:%s\n", player1.ToString())
player1.Win()
player2.Lose()
} else if hand1.IsWeakerThan(hand2) {
fmt.Printf("Winner:%s\n", player2.ToString())
player1.Lose()
player2.Win()
} else {
fmt.Println("Even...")
player1.Even()
player2.Even()
}
}
fmt.Println("Total Result:")
fmt.Println(player1.ToString())
fmt.Println(player2.ToString())
}
func main() {
startMain()
}
(5) その他
ジャンケンの勝敗を管理します。
package strategy
// Const Value for HandGame
const (
handValueGUU = iota
handValueCHO
handValuePAA
)
//Hand is struct
type Hand struct {
handValue int
}
var hands []*Hand
func init() {
hands = []*Hand{
&Hand{handValueGUU},
&Hand{handValueCHO},
&Hand{handValuePAA},
}
}
func getHand(handValue int) *Hand {
return hands[handValue]
}
// IsStrongerThan func can judge result of handGame as Winner
func (myHand *Hand) IsStrongerThan(opponentHand *Hand) bool {
return myHand.fight(opponentHand) == 1
}
// IsWeakerThan func can judge result of handGame as Looser
func (myHand *Hand) IsWeakerThan(opponentHand *Hand) bool {
return myHand.fight(opponentHand) == -1
}
func (myHand *Hand) fight(opponentHand *Hand) int {
if myHand == opponentHand {
return 0
} else if (myHand.handValue+1)%3 == opponentHand.handValue {
return 1
} else {
return -1
}
}