設計模式 in Go: Strategy
行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。
今天我們開始第 9 個行爲模式的學習 —— Strategy(策略模式)。
問題背景:
策略模式用於當有多種可以互換的算法或策略來完成特定任務時。使用這些算法的客戶端不需要知道算法的具體實現細節。
解決方案:
定義一個所有策略都將使用的接口。創建多個實現此公共接口的策略類。上下文對象將接收一個策略對象,並將工作委託給該對象,而不是自行完成工作。
Sports Rank List / Score Board
以下是一個示例,假設我們希望擁有不同的排名列表,並且這些列表的最大元素數量各不相同。我們知道不同長度的列表應該採用不同的排序算法以滿足性能要求。好的,讓我們使用策略模式來解決這個排名列表問題。
Strategy Pattern
示例代碼:
package strategy
import (
"sort"
)
// The Sorter interface declares a method for sorting
type Sorterinterface {
Sort(values []RankItem)
}
// InsertSort implements insert sort
type InsertSortstruct{}
func(b *InsertSort) Sort(values []RankItem) {
// Insert sort implementation , well, if len(values) < 12,
// sort.Slice(values, lessfunc) indeed uses insert sort.
sort.Slice(values,func(i, jint)bool {
return values[i].Score > values[j].Score
})
}
// QuickSort implements quicksort
type QuickSortstruct{}
func(q *QuickSort) Sort(values []RankItem) {
// Quicksort implementation
sort.Slice(values,func(i, jint)bool {
return values[i].Score > values[j].Score
})
}
// NewRankList creates a new rank list
funcNewRankList(capint, sorter Sorter) *RankList {
return &RankList{
capacity:cap,
sorter: sorter,
}
}
// RankItem is a ranked item in the rank list
type RankItemstruct {
Namestring
Scoreint64
}
// RankList maintains a list of ranked items, it's the context of
// the strategy pattern, it will use the proper strategy to sort the
// items.
type RankListstruct {
capacityint
items []RankItem
sorter Sorter
}
func(r *RankList) AddItem(namestring, scoreint64) {
r.items =append(r.items, RankItem{name, score})
r.sorter.Sort(r.items)
iflen(r.items) >= r.capacity {
r.items = r.items[:r.capacity]
}
}
func(r *RankList) Items() []RankItem {
return r.items
}
運行測試以演示:
package strategy_test
import (
"strategy"
"testing"
)
funcTestStrategy(t *testing.T) {
basketballScores := []strategy.RankItem{
{Name:"John", Score:100},
{Name:"Bob", Score:50},
{Name:"Alice", Score:60},
{Name:"Carol", Score:70},
}
basketballScoreBoard := strategy.NewRankList(10, &strategy.InsertSort{})
for _, v :=range basketballScores {
basketballScoreBoard.AddItem(v.Name, v.Score)
}
t.Logf("basketball scoreboard: %v", basketballScoreBoard.Items())
marathonScores := []strategy.RankItem{
{Name:"John", Score:100},
{Name:"Bob", Score:50},
{Name:"Alice", Score:60},
{Name:"Carol", Score:70},
{Name:"David", Score:80},
{Name:"Eric", Score:90},
{Name:"Frank", Score:80},
{Name:"George", Score:90},
{Name:"Harry", Score:80},
{Name:"Ivan", Score:80},
{Name:"Jerry", Score:80},
{Name:"Kevin", Score:80},
{Name:"Larry", Score:80},
{Name:"Mary", Score:80},
}
marathonScoresBoard := strategy.NewRankList(20, &strategy.QuickSort{})
for _, v :=range marathonScores {
marathonScoresBoard.AddItem(v.Name, v.Score)
}
t.Logf("marathon scoreboard: %v", marathonScoresBoard.Items())
}
Running tool: /opt/go/bin/gotest -timeout 30s -run ^TestStrategy$ strategy -v -count=1 -timeout=1m
=== RUN TestStrategy
strategy_test.go:19: basketball scoreboard: [{John 100} {Carol 70} {Alice 60} {Bob 50}]
strategy_test.go:41: marathon scoreboard: [{John 100} {Eric 90} {George 90} {David 80} {Frank 80} {Harry 80} {Ivan 80} {Jerry 80} {Kevin 80} {Larry 80} {Mary 80} {Carol 70} {Alice 60} {Bob 50}]
--- PASS: TestStrategy (0.00s)
PASS
ok strategy 0.001s
ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。
策略模式的變體:
-
將策略作爲 context 構造函數參數傳遞,context 將其作爲引用。
-
允許客戶端在運行時動態更改 context 關聯的策略,context 定義一個設置方法以允許更改策略。
與其他模式關係:
-
策略模式促進了開閉原則:可添加新策略而無需更改現有使用方代碼。
-
策略封裝了算法實現:可在不影響使用它的上下文的情況下發生變化。
-
策略與狀態模式類似:但狀態模式允許根據狀態動態改變算法,而策略模式需要客戶端明確選擇策略。一旦選擇了策略,則不能更改。
本文總結:
策略模式封裝了可互換的算法,並將客戶端與所使用的策略實現細節解耦。它允許在運行時切換策略以改變行爲。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Q30YsWnjT-t8cVSheGhfVA