こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるTemplate Methodパターンについて紹介します。
<目次>
デザインパターンまとめ
シチュエーション
Template Method パターンは、テンプレートの機能を持つパターンです。
テンプレートとは、鋳型、雛型、定型書式などの意味を持つ英単語で、何らかの物やデータを作成する際の雛形になります。
例えば、10回勝負のジャンケンをするとします。その際の手順は、以下のようになるでしょう。
- ゲームをスタート
- じゃんけんをする
- 勝敗がわかる
- 2~3を10回繰り返す
- 勝者がわかる
この1~5の流れをテンプレート化します。これにより、例えば、チェスだったり、カードゲームだったり、上記のパターンに当てはまるゲームの場合、プログラムを一から作り直すことをせず、テンプレートを利用することでこの流れに沿ったゲームを作成することが可能になります。
Template Methodパターンの適用
早速、Template Methodパターンで、プログラミングしていきましょう。
テンプレートインターフェース
最初に、テンプレート(Game)インターフェースを作成します。
1 2 3 4 5 6 |
type Game interface { Start() HaveWinner() TakeTurn() WinningPlayer() int } |
テンプレートを利用するプログラムは、すべてStart, HaveWinner, TakeTurn, WInningPlayerメソッドを実装する必要があります。
Template Methodの作成
次に、Template Methodを作成します。
1 2 3 4 5 6 7 |
func PlayGame(game Game) { game.Start() for !game.HaveWinner() { game.TakeTurn() } fmt.Printf("おめでとう! プレイヤー%dの勝ちです!!\n", game.WinningPlayer()) } |
Gameインターフェースを実装したインスタンスは、playGameを利用することができます。playGameの流れそのものがテンプレートです。
ゲーム1: じゃんけん
じゃんけんをプログラミングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// ゲーム1: じゃんけん type RockPaperScissors struct { turn, maxTurns, currentPlayer int } func NewGameOfRockPaperScissors() Game { return &RockPaperScissors{1, 10, 0} } func (c *RockPaperScissors) Start() { fmt.Println("じゃんけんを開始します。") } func (c *RockPaperScissors) HaveWinner() bool { return c.turn == c.maxTurns } func (c *RockPaperScissors) TakeTurn() { c.turn++ fmt.Printf("ターン%d, プレイヤー%d\n", c.turn, c.currentPlayer) c.currentPlayer = (c.currentPlayer + 1) % 2 } func (c *RockPaperScissors) WinningPlayer() int { return c.currentPlayer } |
ゲーム2: チェス
チェスをプログラミングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// ゲーム2: チェス type Chess struct { turn, maxTurns, currentPlayer int } func NewGameOfChess() Game { return &Chess{1, 10, 0} } func (c *Chess) Start() { fmt.Println("チェスを開始します。") } func (c *Chess) HaveWinner() bool { return c.turn == c.maxTurns } func (c *Chess) TakeTurn() { c.turn++ fmt.Printf("ターン%d, プレイヤー%d\n", c.turn, c.currentPlayer) c.currentPlayer = (c.currentPlayer + 1) % 2 } |
使ってみよう!
じゃんけんもチェスもGameインターフェースを満たすStart, HaveWinner, TakeTurn, WInningPlayerメソッドを実装しているので、PlayGame関数を利用できます。
1 2 3 4 5 6 7 8 9 10 11 |
func main() { // ゲーム1: じゃんけん rps := NewGameOfRockPaperScissors() PlayGame(rps) fmt.Println("----------------------") // ゲーム2: チェス chess := NewGameOfChess() PlayGame(chess) } |
プログラムを実行しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ go run main.go じゃんけんを開始します。 ターン2, プレイヤー0 ターン3, プレイヤー1 ターン4, プレイヤー0 ターン5, プレイヤー1 ターン6, プレイヤー0 ターン7, プレイヤー1 ターン8, プレイヤー0 ターン9, プレイヤー1 ターン10, プレイヤー0 おめでとう! プレイヤー1の勝ちです!! ---------------------- チェスを開始します。 ターン2, プレイヤー0 ターン3, プレイヤー1 ターン4, プレイヤー0 ターン5, プレイヤー1 ターン6, プレイヤー0 ターン7, プレイヤー1 ターン8, プレイヤー0 ターン9, プレイヤー1 ターン10, プレイヤー0 おめでとう! プレイヤー1の勝ちです!! |
OKですね。
Functional Template Mthod
PlayGame関数を以下のように実装することができます。
1 2 3 4 5 6 7 8 9 |
func PlayGame(start, takeTurn func(), haveWinner func() bool, winningPlayer func() int) { start() for !haveWinner() { takeTurn() } fmt.Printf("おめでとう! プレイヤー%dの勝ちです!!\n", winningPlayer()) } |
PlayGameの中に、テンプレートとなる関数を渡しました。
この関数は、次のように利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
func main() { turn, maxTurns, currentPlayer := 1, 10, 0 rpsStart := func() { fmt.Println("じゃんけんを開始します。") } takeTurn := func() { turn++ fmt.Printf("ターン%d, プレイヤー%d\n", turn, currentPlayer) currentPlayer = (currentPlayer + 1) % 2 } haveWinner := func() bool { return turn == maxTurns } winningPlayer := func() int { return currentPlayer } PlayGame(rpsStart, takeTurn, haveWinner, winningPlayer) turn, maxTurns, currentPlayer = 1, 10, 0 chessStart := func() { fmt.Println("チェスを開始します。") } takeTurn = func() { turn++ fmt.Printf("ターン%d, プレイヤー%d\n", turn, currentPlayer) currentPlayer = (currentPlayer + 1) % 2 } haveWinner = func() bool { return turn == maxTurns } winningPlayer = func() int { return currentPlayer } PlayGame(chessStart, takeTurn, haveWinner, winningPlayer) } |
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ go run sample3/main.go じゃんけんを開始します。 ターン2, プレイヤー0 ターン3, プレイヤー1 ターン4, プレイヤー0 ターン5, プレイヤー1 ターン6, プレイヤー0 ターン7, プレイヤー1 ターン8, プレイヤー0 ターン9, プレイヤー1 ターン10, プレイヤー0 おめでとう! プレイヤー1の勝ちです!! チェスを開始します。 ターン2, プレイヤー0 ターン3, プレイヤー1 ターン4, プレイヤー0 ターン5, プレイヤー1 ターン6, プレイヤー0 ターン7, プレイヤー1 ターン8, プレイヤー0 ターン9, プレイヤー1 ターン10, プレイヤー0 おめでとう! プレイヤー1の勝ちです!! |
OKですね。
まとめ
Template Methodパターンは、実務に活かせそうな考え方ですね。
共通する流れで処理したい場合に用いると良いと思います。個別に処理をしたい場合は、テンプレート関数から得た結果を加工すれば良さそうなので、使い勝手もいいですね。
利用するとしたらコマンドラインツールとかになると思います。ログの処理などに利用できそうです。
それでは、また!
Go言語まとめ
ソースコード
Template Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
package main import "fmt" type Game interface { Start() HaveWinner() bool TakeTurn() WinningPlayer() int } func PlayGame(game Game) { game.Start() for !game.HaveWinner() { game.TakeTurn() } fmt.Printf("おめでとう! プレイヤー%dの勝ちです!!\n", game.WinningPlayer()) } // ゲーム1: じゃんけん type RockPaperScissors struct { turn, maxTurns, currentPlayer int } func NewGameOfRockPaperScissors() Game { return &RockPaperScissors{1, 10, 0} } func (c *RockPaperScissors) Start() { fmt.Println("じゃんけんを開始します。") } func (c *RockPaperScissors) HaveWinner() bool { return c.turn == c.maxTurns } func (c *RockPaperScissors) TakeTurn() { c.turn++ fmt.Printf("ターン%d, プレイヤー%d\n", c.turn, c.currentPlayer) c.currentPlayer = (c.currentPlayer + 1) % 2 } func (c *RockPaperScissors) WinningPlayer() int { return c.currentPlayer } // ゲーム2: チェス type Chess struct { turn, maxTurns, currentPlayer int } func NewGameOfChess() Game { return &Chess{1, 10, 0} } func (c *Chess) Start() { fmt.Println("チェスを開始します。") } func (c *Chess) HaveWinner() bool { return c.turn == c.maxTurns } func (c *Chess) TakeTurn() { c.turn++ fmt.Printf("ターン%d, プレイヤー%d\n", c.turn, c.currentPlayer) c.currentPlayer = (c.currentPlayer + 1) % 2 } func (c *Chess) WinningPlayer() int { return c.currentPlayer } func main() { // ゲーム1: じゃんけん rps := NewGameOfRockPaperScissors() PlayGame(rps) fmt.Println("----------------------") // ゲーム2: チェス chess := NewGameOfChess() PlayGame(chess) } |
Functional Template Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package main import "fmt" func PlayGame(start, takeTurn func(), haveWinner func() bool, winningPlayer func() int) { start() for !haveWinner() { takeTurn() } fmt.Printf("おめでとう! プレイヤー%dの勝ちです!!\n", winningPlayer()) } func main() { turn, maxTurns, currentPlayer := 1, 10, 0 rpsStart := func() { fmt.Println("じゃんけんを開始します。") } takeTurn := func() { turn++ fmt.Printf("ターン%d, プレイヤー%d\n", turn, currentPlayer) currentPlayer = (currentPlayer + 1) % 2 } haveWinner := func() bool { return turn == maxTurns } winningPlayer := func() int { return currentPlayer } PlayGame(rpsStart, takeTurn, haveWinner, winningPlayer) turn, maxTurns, currentPlayer = 1, 10, 0 chessStart := func() { fmt.Println("チェスを開始します。") } takeTurn = func() { turn++ fmt.Printf("ターン%d, プレイヤー%d\n", turn, currentPlayer) currentPlayer = (currentPlayer + 1) % 2 } haveWinner = func() bool { return turn == maxTurns } winningPlayer = func() int { return currentPlayer } PlayGame(chessStart, takeTurn, haveWinner, winningPlayer) } |
コメントを残す
コメントを投稿するにはログインしてください。