Goでデザインパターン (Strategyパターン)

Goデザインパターン

Strategyパターンとは

振る舞いに関するデザインパターンの1つ。あることを様々な方法で行うクラスに対して、アルゴリズム部分を別クラスに抽出することにより、場面に応じてそれらを切替可能にすることができる。
例えば利用端末の性能によって、「メモリ使用量が大きいが高速なアルゴリズム」「メモリ使用量は小さいが低速なアルゴリズム」等を動的に切り替えることができるようになる。

長所

  • 実行時にオブジェクト内で利用されるアルゴリズムを動的に変更可能
  • アルゴリズムの実装の詳細を利用する側から隠蔽できる
  • 新規ストラテジーの追加の際にコンテキストの変更が必要ない (開放閉鎖の原則)

短所

  • クライアント側は適切なストラテジーをコンテキストに渡す必要がある。そのため各ストラテジーを間の違いを知っている必要がある。
  • 関数型をサポートしている言語ではアルゴリズムにあたる関数を直接渡せば良いため、パターンを利用する必要がない場合がある

利用場面

  • オブジェクト内で利用するアルゴリズムを動的に変更したい場合
  • 一部の振る舞いのみが異なるクラスが複数存在する場合
  • クラスのビジネスロジックとアルゴリズムの実装部分を分離したい場合
  • アルゴリズムの切り替えのための条件分岐が見受けられる場合

クラス図

  • Context (コンテキスト)
    具象ストラテジーの内いずれかの参照を保持し、必要に応じてそれを利用する。具象ストラテジーのオブジェクトとはStrategy Interfaceを通じて通信する。
  • Strategy (ストラテジー)
    具象ストラテジーで実装するべきインターフェースを宣言する。コンテキストが利用するのはここで宣言されたインターフェースのみ。
  • Concrete Strategy (具象ストラテジー)
    Strategyを実装する。コンテキストが利用するアルゴリズムをそれぞれ異なる方法で実装する。

実装

Context

package main

// Strategyで宣言されたインターフェースを利用し、種々のアルゴリズムを実行する
// インターフェースを利用するため、具象Strategyは交換可能
type Context struct {
  // 具象Strategyの参照を保持する
	exchanger Exchanger
}

func NewContext() *Context {
	return &Context{}
}

// 作業の一部をStrategyオブジェクトに委任することで、種々のアルゴリズムに対応した処理を実行する
func (c *Context) Exchange(yen float64) float64 {
	return c.exchanger.Exchange(yen)
}

func (c *Context) Rate() float64 {
	return c.exchanger.GetRate()
}

func (c *Context) SetExchanger(e Exchanger) {
	c.exchanger = e
}

Strategy

package main

// 種々のアルゴリズムに共通のインターフェースを宣言する。
type Exchanger interface {
	Exchange(yen float64) float64
	GetRate() float64
}

Concrete Strategy

package main

type DollarExchanger struct {
	rate float64
}

func NewDollarExchanger() *DollarExchanger {
	return &DollarExchanger{rate: 0.0193}
}

func (d *DollarExchanger) Exchange(yen float64) float64 {
	return d.rate * yen
}

func (d *DollarExchanger) GetRate() float64 {
	return d.rate
}
package main

type EuroExchanger struct {
	rate float64
}

func NewEuroExchanger() *EuroExchanger {
	return &EuroExchanger{rate: 0.00899}
}

func (e *EuroExchanger) Exchange(yen float64) float64 {
	return e.rate * yen
}

func (e *EuroExchanger) GetRate() float64 {
	return e.rate
}

動作確認

package main

import (
	"fmt"
)

func main() {
	c := NewContext()
	ed := NewDollarExchanger()
	ee := NewEuroExchanger()

	yenList := []float64{100.0, 145.3, 1009.3}

  // クライアント側は具象ストラテジーを一つ選び、コンテキストに渡す
  // クライアントは正しいストラテジーを選択するために、ストラテジー間の違いを理解する必要がある。
	c.SetExchanger(ed)
	fmt.Println("円 -> ドル")
	for _, y := range yenList {
		fmt.Printf("%.3f円 -> %.3fドル\n", y, c.Exchange(y))
	}

	c.SetExchanger(ee)
	fmt.Println("円 -> ユーロ")
	for _, y := range yenList {
		fmt.Printf("%.3f円 -> %.3fユーロ\n", y, c.Exchange(y))
	}
}
>> go run .
円 -> ドル
100.000円 -> 1.930ドル   
145.300円 -> 2.804ドル   
1009.300円 -> 19.479ドル 
円 -> ユーロ
100.000円 -> 0.899ユーロ 
145.300円 -> 1.306ユーロ 
1009.300円 -> 9.074ユー

補足

Bridgeパターンとの違い

クラス図を見ればわかる通り、StrategyパターンとBridgeパターンはほぼ同じ構造をしている。この2つのパターンは解決する問題が異なっている。(いまいち理解し切れていないので要確認)

  • Bridgeパターン
    構造に関するデザインパターンの一つ。独立した次元でクラスを拡張する必要がある場合に、抽象化層と実装を分離することで、それぞれを個別に改修・拡張できるようにしたのがBridgeパターン。
    →高レイヤ層に適応される?クラスそれぞれを独立して開発できることを伝えたい?
  • Strategyパターン
    振る舞いに関するデザインパターンの一つ。種々のアルゴリズムを切り替えることで、振る舞いを変更できるようにしたのがStrategyパターン。
    →低レイヤ層に適応される?振る舞いが切り替えられることを伝えたい?

デザインパターンはクラスに特定の構造を持たせることだけに主眼を置いたものではない。そのパターンを採用した意図や解決しようとしている問題を伝えるためにも利用される。

コメント

タイトルとURLをコピーしました