こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるStrategyパターンについて紹介します。
デザインパターンまとめ
シチュエーション
Strategy は「戦略」を意味する英単語です。Strategy パターンは、処理の切り替えや追加が簡単に行えるようにするパターンになります。
例えば、ログ出力を例に考えてみましょう。
ログを出力する方法はたくさんあります。例えば、テキストファイルに出力する、CSVファイルに出力する、標準出力に出力する、などです。これは、Strategyパターンを当てはめることができそうなので、サンプルを実装してみましょう。
Strategyパターンの適用
Strategyパターンで切り替える処理は、ログの出力形式です。これを念頭に置いて、プログラミングしていきましょう。
Strategy(戦略)の定数化
Strategyを定数で定義します。
1 2 3 4 5 6 7 |
type OutputFormat int const ( Terminal OutputFormat = iota Text Csv ) |
Terminal(標準出力)、Text(テキストファイル)、Csv(CSVファイル)の3つの戦略を定義しました。
Strategyインターフェース
Strategyが共通で持つメソッドを、インターフェースに定義します。
1 2 3 4 |
type LogStrategy interface { Write(log string) Read() } |
Strategyの実装
3つのStrategyを実装します。これらは、Strategyインターフェースを実装させるため、Write, Readメソッドを持ちます。
1. TerminalStrategy
標準出力のStrategyを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ype TerminalStrategy struct { logs []string } func (t *TerminalStrategy) Write(text string) { t.logs = append(t.logs, text) } func (t *TerminalStrategy) Read() { for _, t := range t.logs { fmt.Println(t) } } |
2. TextStrategy
テキストファイルのStrategyを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
type TextStrategy struct{} func (t *TextStrategy) Write(text string) { var textToBytes []byte textToBytes = append(textToBytes, text...) ioutil.WriteFile("out.txt", textToBytes, 0644) } func (t *TextStrategy) Read() { bytes, err := ioutil.ReadFile("out.txt") if err != nil { panic(err) } fmt.Println(string(bytes)) } |
3. CsvStrategy
CSVファイルのStrategyを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
type CsvStrategy struct{} func (t *CsvStrategy) Write(text string) { file, _ := os.Create("out.csv") defer file.Close() writer := csv.NewWriter(file) writer.Write([]string{text}) writer.Flush() } func (t *CsvStrategy) Read() { bytes, err := ioutil.ReadFile("out.csv") if err != nil { panic(err) } fmt.Println(string(bytes)) } |
LogProcessorの作成
Strategyを利用するLogProcessorを実装しましょう。
1 2 3 4 5 6 7 |
type LogProcessor struct { logStrategy LogStrategy } func NewLogProcessor(logStrategy LogStrategy) *LogProcessor { return &LogProcessor{logStrategy: logStrategy} } |
Strategyの切り替え
先ほど実装したLogProcessorで、Strategyの切り替えを行います。
以下のメソッドを実装しましょう。
1 2 3 4 5 6 7 8 9 10 |
func (l *LogProcessor) SetOutputFormat(fmt OutputFormat) { switch fmt { case Terminal: l.logStrategy = &TerminalStrategy{} case Text: l.logStrategy = &TextStrategy{} case Csv: l.logStrategy = &CsvStrategy{} } } |
これで、OutputFormat(定数)を渡すことで、Strategyを切り替えることが可能になりました。
LogProcessからStrategyを呼び出す
最後に、LogProcessorからStrategyを呼び出すメソッドを実装します。
1 2 3 4 5 6 7 |
func (l *LogProcessor) Write(text string) { l.logStrategy.Write(text) } func (l *LogProcessor) Read() { l.logStrategy.Read() } |
使ってみよう
ここまで実装したソースコードを使ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func main() { // log output to terminal lp := NewLogProcessor(&TerminalStrategy{}) lp.Write("this is a terminal log") lp.Read() // log output to text lp.SetOutputFormat(Text) lp.Write("this is a text log") lp.Read() // log output to csv lp.SetOutputFormat(Csv) lp.Write("this is a csv log") lp.Read() } |
最初に、NewLogProcessorコンストラクタで、標準出力へログを出力するインスタンスを作成しました。
その後、SetOutputFormatメソッドで、StrategyをText -> CSVへと切り替えています。
プログラムを実行します。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go this is a terminal log this is a text log this is a csv log $ tree . . ├── main.go ├── out.csv ├── out.txt |
OKですね!
まとめ
Strategyパターンは、結構使えそうですね。
入力・出力形式が異なるファイル処理の実装、円やドルなど単位の異なる金額計算など、処理内容は同じでも、ものの要素が異なる処理を実装したいときに便利そうです。
それでは、また!
Go言語まとめ
ソースコード
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
package main import ( "encoding/csv" "fmt" "io/ioutil" "os" ) type OutputFormat int const ( Terminal OutputFormat = iota Text Csv ) type LogStrategy interface { Write(log string) Read() } type TerminalStrategy struct { logs []string } func (t *TerminalStrategy) Write(text string) { t.logs = append(t.logs, text) } func (t *TerminalStrategy) Read() { for _, t := range t.logs { fmt.Println(t) } } type TextStrategy struct{} func (t *TextStrategy) Write(text string) { var textToBytes []byte textToBytes = append(textToBytes, text...) ioutil.WriteFile("out.txt", textToBytes, 0644) } func (t *TextStrategy) Read() { bytes, err := ioutil.ReadFile("out.txt") if err != nil { panic(err) } fmt.Println(string(bytes)) } type CsvStrategy struct{} func (t *CsvStrategy) Write(text string) { file, _ := os.Create("out.csv") defer file.Close() writer := csv.NewWriter(file) writer.Write([]string{text}) writer.Flush() } func (t *CsvStrategy) Read() { bytes, err := ioutil.ReadFile("out.csv") if err != nil { panic(err) } fmt.Println(string(bytes)) } type LogProcessor struct { logStrategy LogStrategy } func NewLogProcessor(logStrategy LogStrategy) *LogProcessor { return &LogProcessor{logStrategy: logStrategy} } func (l *LogProcessor) SetOutputFormat(fmt OutputFormat) { switch fmt { case Terminal: l.logStrategy = &TerminalStrategy{} case Text: l.logStrategy = &TextStrategy{} case Csv: l.logStrategy = &CsvStrategy{} } } func (l *LogProcessor) Write(text string) { l.logStrategy.Write(text) } func (l *LogProcessor) Read() { l.logStrategy.Read() } func main() { // log output to terminal lp := NewLogProcessor(&TerminalStrategy{}) lp.Write("this is a terminal log") lp.Read() // log output to text lp.SetOutputFormat(Text) lp.Write("this is a text log") lp.Read() // log output to csv lp.SetOutputFormat(Csv) lp.Write("this is a csv log") lp.Read() } |
コメントを残す
コメントを投稿するにはログインしてください。