Golang 策略設計模式
本文主要介紹了在 Golang 中實現策略設計模式(Strategy Design Pattern)的方法和優勢。策略設計模式是一種用於處理多種相似算法或行爲的設計模式,允許在運行時動態切換算法。原文: Strategy Design Pattern in Golang[1]
假設有一個名爲 PaymentStrategy
的接口,包含一個名爲 Pay()
的方法,有兩種名爲 CreditCardStrategy
和 DebitCardStrategy
的類型實現了該接口。我們希望根據不同情況,PaymentStrategy.Pay()
可以執行 CreditCardStrategy
類型或 DebitCardStrategy
類型的實現。如何才能做到這一點呢?
可以通過策略設計模式來實現這一目標,但首先我們需要知道什麼是策略設計模式?
策略模式處理的情況是,有多種算法或行爲(算法可能有不同的實現方式,但目的類似),這些算法或行爲可以互換或動態使用。例如,可能有不同的排序算法,如冒泡排序、合併排序和快速排序,目的都是對一個集合進行排序。另一個例子是 Pay()
方法,對於 CreditCardStrategy
類型和 DebitCardStrategy
類型,Pay()
方法的實現可能不同,但兩種方法的目的是一樣的。
上面提到不同算法,每種算法都封裝在自己的類或類型中,這個類或類型代表算法的獨立策略,這樣的類被稱爲策略類。策略類提供一組方法,用於定義策略的執行方式。例如,CreditCardStrategy
和 DebitCardStrategy
這兩種類型都實現了 Pay()
方法。因此,我們可以說每個 Pay()
方法都封裝了一個類型,每個類型代表一個單獨的策略。例如,CreditCardStrategy
類型表示使用信用卡付款,而 DebitCardStrategy
類型表示將使用借記卡付款。也就是說,這兩種類型分別代表兩種策略,這就是它們被稱爲策略類或策略類型的原因。
在策略設計模式中,我們可以在運行時動態切換或選擇策略,這意味着可以在不修改代碼的情況下改變對象使用的算法。策略的選擇可以基於各種因素,如用戶輸入、配置設置或特定條件。例如,如果用戶需要,可以使用 CreditCardStrategy
,也可以使用 DebitCardStrategy
。爲此,我們不需要在編碼中做任何修改,而是可以動態處理。
策略模式促進了使用算法的代碼(客戶端代碼)與算法的實際實現(策略類)之間的松耦合。客戶端代碼通過通用接口或抽象與策略進行交互,而無需瞭解每個策略實現的具體細節。這使得代碼庫具有更高的靈活性、可維護性和可擴展性。例如,這裏的通用接口是 PaymentStrategy
,客戶端代碼只需要與 PaymentStrategy
方法通信,而不需要與策略通信。也就是說,不需要考慮 CreditCardStrategy
類型或 DebitCardStrategy
,客戶代碼與它們沒有任何關係。代碼只與通用接口相關聯,根據實際情況,通用接口將決定實施哪一個。
因此,從這裏我們可以列出策略設計模式的要點:
-
可互換算法
-
將每種算法封裝爲單獨的類
-
策略在運行時可以互換
-
避免算法實現與客戶端代碼緊耦合
舉個例子:
package main
import "fmt"
type PaymentStrategy interface {
Pay()
}
// Credit Card Strategy Type
type CreditCardStrategy struct{}
func (c *CreditCardStrategy) Pay() {
fmt.Println("Paying using Credit Card")
}
// Debit Card Strategy Type
type DebitCardStrategy struct{}
func (d *DebitCardStrategy) Pay() {
fmt.Println("Paying using Debit Card")
}
// Visa Card Strategy Type
type VisaCardStrategy struct{}
func (v *VisaCardStrategy) Pay() {
fmt.Println("Paying using Visa Card")
}
// This type sets the strategy dynamically
type PaymentMethod struct {
paymentStrategy PaymentStrategy
}
func (p *PaymentMethod) setPaymentMethodStrategy(strategy PaymentStrategy) {
p.paymentStrategy = strategy
}
func (p *PaymentMethod) checkOut() {
p.paymentStrategy.Pay()
}
func main() {
paymentMethod := &PaymentMethod{}
// Credit Card
creditCardStrategy := &CreditCardStrategy{}
paymentMethod.setPaymentMethodStrategy(creditCardStrategy)
paymentMethod.checkOut()
// Debit Card
debitCardStrategy := &DebitCardStrategy{}
paymentMethod.setPaymentMethodStrategy(debitCardStrategy)
paymentMethod.checkOut()
// Visa Card
visaCardStrategy := &VisaCardStrategy{}
paymentMethod.setPaymentMethodStrategy(visaCardStrategy)
paymentMethod.checkOut()
}
示例中有三種不同的支付策略,分別是 CreditCardStrategy
、VisaCardStrategy
和 DebitCardStrategy
。每種策略的支付流程不同,但目的相同,都是支付。
PaymentMethod
類型處理策略的選擇。代碼只需與 PaymentMethod
聯繫,PaymentMethod
根據情況決定選擇哪種策略。爲此,我們不需要對 PaymentMethod
類型做任何修改。或者說,我們不需要在 PaymentMethod
類型中定義任何有關策略的內容。這就是策略設計模式的魅力所在。我們不需要提及策略,但可以使用它們。這就是所謂的松耦合。
讓我們再舉一個例子:
package main
import "fmt"
type Animal interface {
bark()
}
type Dog struct{}
func (d *Dog) bark() {
fmt.Println("Bark by Dog")
}
type Cat struct{}
func (c *Cat) bark() {
fmt.Println("Bark by a Cat")
}
type Bark struct {
animal Animal
}
func (b *Bark) setBarkType(barkType Animal) {
b.animal = barkType
}
func (b *Bark) checkBark() {
b.animal.bark()
}
func main() {
animalBark := &Bark{}
dogBark := &Dog{}
animalBark.setBarkType(dogBark)
animalBark.checkBark()
catBark := &Cat{}
animalBark.setBarkType(catBark)
animalBark.checkBark()
}
最後舉一個真實的例子。
我們以文本編輯器爲例,文本編輯器需要支持不同的文本格式選項,如粗體、斜體和下劃線。對於不同的文本格式選項,並不需要在文本編輯器類中實現這些格式選項,而是可以根據選擇應用策略模式。
每個文本格式選項(粗體、斜體、下劃線)都封裝在單獨的策略類中,每個策略類都實現了通用接口或抽象類,定義了應用格式化的行爲。文本編輯器類有對當前格式化策略的引用,並用它對選定文本應用格式化。在運行時,用戶可以在不同的格式化策略之間切換,文本編輯器會應用所選的格式化,而無需修改其核心代碼。
通過在這種情況下使用策略模式,文本編輯器實現了靈活性,允許用戶在運行時選擇格式化策略,而無需將文本編輯器代碼與特定格式化實現緊密耦合。這種關注點分離和策略的可互換性正是策略設計模式的精髓所在。
策略設計模式的優勢
-
改進代碼組織:通過使用策略模式,可以將算法行爲分離到不同的策略類或類型中,從而實現代碼的簡潔和可重用性。每個策略類專注於特定算法,使代碼更易於理解和維護。
-
增強靈活性和可維護性:以
PaymentStrategy
爲例。假設我們引入了新的PaymentStrategy
,不需要對PaymentMethod
類做任何修改,只需聲明一個實現了Pay()
方法的類型即可。這將增加靈活性。 -
可重用性:我們可以在需要類似行爲的不同對象或系統中重複使用策略。一旦定義了策略,就可以輕鬆將其插入多個上下文中,而無需重複代碼。
-
可讀性:由於每個策略都有獨立的策略類及其實現方式,代碼變得可讀、可辨。
-
可測試性:代碼變得可讀,測試代碼也就更容易。如果出現任何問題,也很容易修復錯誤,可以很容易找到錯誤,並知道如何處理。
策略設計模式的注意事項
就策略設計模式而言,必須牢記以下幾點:
-
類數量增加:如果策略數量增加,類的數量也會增加。雖然這可以改善代碼組織,但也會增加整體設計的複雜性,代碼庫會變得更大。重要的是要達到平衡,避免創建過多的類。
-
策略的架構:以適當層次或順序定義策略非常重要。我們需要查找重複的類,不同的類可能有相同的實現,爲此可以使用單個類。
-
初始化以及策略選擇:需要決定何時以及如何初始化策略,以及如何在運行時選擇合適的策略,需要非常謹慎的處理這一點。這可以通過依賴注入、配置文件或基於特定條件或用戶輸入的動態選擇來實現。
你好,我是俞凡,在 Motorola 做過研發,現在在 Mavenir 做技術工作,對通信、網絡、後端架構、雲原生、DevOps、CICD、區塊鏈、AI 等技術始終保持着濃厚的興趣,平時喜歡閱讀、思考,相信持續學習、終身成長,歡迎一起交流學習。爲了方便大家以後能第一時間看到文章,請朋友們關注公衆號 "DeepNoMind",並設個星標吧,如果能一鍵三連 (轉發、點贊、在看),則能給我帶來更多的支持和動力,激勵我持續寫下去,和大家共同成長進步!
參考資料
[1]
Strategy Design Pattern in Golang: https://blog.stackademic.com/strategy-design-pattern-in-golang-52024a97d417
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8svtzN5cHZkJChwYSXIinQ