Go 設計模式:責任鏈和函數選項,讓你的代碼更優雅!
最近接觸到越來越多有歷史 “沉澱” 的 Go 項目,深感設計模式和及時干預的重要性。近期會分享一些設計模式,一起學習代碼設計!
今天的分享的設計模式是:責任鏈和函數選項模式。在日常程序裏是比較常用的。很多開源庫中也有使用。
責任鏈模式
責任鏈模式(Chain of Responsibility Pattern)是一種行爲設計模式,它允許對象將請求沿處理程序鏈進行傳遞。
程序鏈既可以處理請求,也可以繼續將請求傳遞給鏈中的下一個處理程序。請求沿着處理程序鏈傳遞,直到請求被處理或到達處理程序鏈的末端。
這類場景的例子包括:事件處理、日誌記錄和錯誤處理等類似攔截器注入的用法。
例子
我們將會創建一個簡單例子來演示 Go 中的責任鏈模式。
在這個例子中,責任鏈將負責驗證請求、打印錯誤日誌和處理請求本身。
具體代碼如下:
package service
import "fmt"
type Service interface {
HelloWorld(name string) (string, error)
}
type service struct {}
func (s service) HelloWorld(name string) (string, error) {
return fmt.Sprintf("Hello World from %s", name), nil
}
type validator struct {
next Service
}
func (v validator) HelloWorld(name string) (string, error) {
if len(name) <= 3 {
return "", fmt.Errorf("name length must be greater than 3")
}
return v.next.HelloWorld(name)
}
type logger struct {
next Service
}
func (l logger) HelloWorld(name string) (string, error) {
res, err := l.next(name)
if err != nil {
fmt.Println("error:", err)
return res, err
}
fmt.Println("HelloWorld method executed successfuly")
return res, err
}
func New() Service {
return logger{
next: validator {
next: service{},
},
}
}
入口 main 函數使用:
package main
import (
"fmt"
"service"
)
func main() {
s := service.New()
res, err := s.HelloWorld("煎魚")
fmt.Println(res, err) // Hello World from 煎魚
res, err := s.HelloWorld("edd")
fmt.Println(res, err) // name length must be greater than 3
}
在本例中,我們創建了一個實現 Service
接口的簡單服務。Service
接口定義了一個方法 HelloWorld
,該方法將名稱作爲參數,並返回一條 Hello World 信息。
我們還創建了兩個額外的結構體 validator
和 logger
,它們都實現了 Service
接口。
validator
結構會驗證名稱的長度,如果長度小於或等於 3,則返回錯誤信息。當 HelloWorld
方法成功執行時,logger
結構會打印錯誤日誌和成功信息。
New
函數通過將 validator
和 logger
結構與 Service
結構鏈起來,創建了一個處理程序鏈。
Service
結構體的方法是鏈中的最後一個處理程序,如果請求通過驗證,它將處理請求。
例子中 next 字段的作用是什麼
next
字段用於保存鏈中的下一個處理程序。
當在一個處理程序上調用 HelloWorld
方法時,會使用 next
字段在鏈中的下一個處理程序上調用 HelloWorld
方法。
對照看可以得知,validator
和 logger
結構體中的 next
字段,是用於鏈式處理程序的關鍵字段。
函數選項模式
函數選項模式(Function Options Pattern)是一種很常見的設計模式,它允許開發人員通過使用 Options 作爲參數,爲函數或方法提供靈活且可定製的行爲。
常用於 Go 庫和框架,爲用戶提供簡潔明瞭的 API。經常在我們使用的基礎庫能看到,例如:grpc-go。
例子
我們有一個用 Go 表示 HTTP 服務器的 Server 結構體作爲基礎。
我們希望爲用戶提供配置 HTTP Server 各方面的功能,例如:監聽的端口、超時時間以及是否啓用日誌記錄等。
具體代碼如下:
package main
import "fmt"
type Server struct {
Host string
Port int
Protocol string
Timeout int
}
type ServerOption func(*Server)
func WithHost(host string) ServerOption {
return func(s *Server) {
s.Host = host
}
}
func WithPort(port int) ServerOption {
return func(s *Server) {
s.Port = port
}
}
func WithProtocol(protocol string) ServerOption {
return func(s *Server) {
s.Protocol = protocol
}
}
func WithTimeout(timeout int) ServerOption {
return func(s *Server) {
s.Timeout = timeout
}
}
func NewServer(options ...ServerOption) *Server {
server := &Server{
Host: "localhost",
Port: 8080,
Protocol: "http",
Timeout: 30,
}
for _, option := range options {
option(server)
}
return server
}
func main() {
server := NewServer(
WithHost("eddycjy.com"),
WithPort(9000),
WithProtocol("https"),
WithTimeout(60),
)
fmt.Printf("Server: %+v\n", server)
}
解決了什麼問題
-
參數數量不斷增加:隨着參數數量的增加,傳統的函數簽名可能會變得臃腫不堪。函數選項模式允許添加新的選項,而無需更改函數簽名或破壞現有代碼。
-
可讀性:當一個函數需要多個參數,尤其是同一類型的參數時,很難記住每個參數的順序和含義。有了函數選項(Option),每個選項都有清晰的標籤,不言自明,從而提高了代碼的可讀性。
-
缺省值:函數選項模式允許你輕鬆地爲選項提供默認值。如果在調用函數時沒有提供選項,就會使用默認值。
-
可選參數:在某些情況下,你可能想讓某些參數成爲可選參數。函數選項模式可以讓你輕鬆做到這一點,並提供一個靈活的接口。
-
封裝和獨立:每個選項都是一個函數,可以包含自己的程序邏輯。這樣就可以封裝每個選項的邏輯,並保持主函數簡潔明瞭。
總結
一個好的設計模式,可以讓你的項目更具有易讀性。程序編寫和使用起來更加的舒心。今天給大家介紹的責任鏈模式和函數可選模式,是非常常用的。
推薦大家可以嘗試應用到自己的 Go 項目中去,想必會有非常大的幫助!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ow1un48RY7qInx4YCQzV1g