詳解Go語言如何對數(shù)據(jù)庫進行CRUD操作
Go語言中的接口
在這里我將不會介紹接口基礎知識,如:接口定義和實現(xiàn)、空接口、類型斷言、v, ok := i.(T)、switch x.(type)、接口嵌套、指針接收器與值接收器實現(xiàn)接口、接口零值 這些基礎概念不作多說,如不太清楚上面接口知識,自行學習補充。這里介紹在開發(fā)中接口實在用法,也是由上面基礎演變而成。
使用接口編程
有這么一個普通場景,上傳并保存文件,三七二十一蹭蹭蹭,一下子搞完,so easy,代碼如下:
項目準備上線,公司購買了OSS存儲,文件資源要存到OSS上,那么如何修改上面的代碼呢? 這個時候接口就可以上場了。 實現(xiàn)如下:
// NOTE: storage.go // 存儲文件接口 type Storage interface { Save(data []byte) (string, error) } // 開發(fā)時候使用本地存儲 type localStorage struct { config *struct{ base string } } func NewLocalStorage(cfg struct{ base string }) *localStorage { return &localStorage{config: &cfg} } func (store localStorage) Save(data []byte) (string, error) { return "http://127.0.0.1/" + store.config.base + "/a.png", nil } // 生產(chǎn)使用oss存儲 type ossStorage struct{} func NewOSSStorage() *ossStorage { return &ossStorage{} } func (store ossStorage) Save(data []byte) (string, error) { return "https://abc.com/oss/a.png", nil } // NOTE: upload.go // 保存文件 func saveFile(store Storage) { var data []byte // 偽代碼 url, _ := store.Save(data) fmt.Println(url) } func main() { var store Storage if os.Getenv("ENV") == "prod" { store = NewOSSStorage() } else { store = NewLocalStorage(struct{ base string }{base: "/static"}) } saveFile(store) }
上面定義Storage
接口,localStorage、ossStorage 都是實現(xiàn)了接口,而 saveFile(store Storage)
接受指針。 這種方式就是接受接口返回結構
,是 Go 語言接口編程中非常經(jīng)典實用模式。
接受接口返回結構
上面已經(jīng)的例子就是使用這個法則,使用這個法則讓代碼變得更加靈活,它是松耦合的,更加方便mock數(shù)據(jù)測試,可以避免一些不必要的bug。
現(xiàn)在舉一個不方便測試的例子:
type Repository struct { DB *sql.DB } func NewRepository(db *sql.DB) *Repository { return &Repository{DB: db} } func (repo *Repository) Insert() { // do some thing } func (repo *Repository) Update() { // do some thing } type Service struct { Repo Repository } func NewService(db *sql.DB) *Service { return &Service{ Repo: *NewRepository(db), } } func main() { // 假設是真實的鏈接 &sql.DB{} srv := NewService(&sql.DB{}) srv.Repo.Insert() srv.Repo.Update() }
上面的例子看著無任何問題???這不很正常。是的很正常,就是測試的時候不方便。 如果要測試起來,你必須要創(chuàng)建一個數(shù)據(jù)庫,連接數(shù)據(jù)庫,創(chuàng)建sql.DB
實例。才能完成測試。如果想簡單mock數(shù)據(jù),就不好辦了。
因此還是建議,使用接口編程。修改如下:
type Repository interface { Insert() Update() } type repository struct { DB *sql.DB } func NewRepository(db *sql.DB) *repository { return &repository{DB: db} } func (repo *repository) Insert() { // do some thing } func (repo *repository) Update() { // do some thing } type Service struct { Repo Repository } func NewService(r Repository) *Service { return &Service{ Repo: r, } } func main() { // 假設是真實的鏈接 &sql.DB{} r := NewRepository(&sql.DB{}) srv := NewService(r) srv.Repo.Insert() srv.Repo.Update() }
接口檢查
上面的例子,func NewRepository(db *sql.DB) *repository
返回的是結構體指針。在編碼過程,很容易忘記實現(xiàn)接口,甚至全部接口都沒實現(xiàn),編譯器也不會報錯。我怎么知道repository
結構體就一定全部實現(xiàn)了Repository
接口呢,對吧。
那這個時候就要用到接口檢查了。 接口檢查的語法是
var _ MyInterface = (*MyStruct)(nil)
上面當我沒有實現(xiàn)Inser方法時就會直接編譯不通過。
這就是接口檢查帶來好處。
擴展接口
假設有這么一場景,在開發(fā)中我們使用到了第三方庫,第三庫提供一個Worker
接口,我們在work、next
兩個方法都使用到Worker
接口,入?yún)⒕褪撬?。這個時候我們希望在work函數(shù)中對Context
增加額外的屬性,提供給next
函數(shù)使用。但現(xiàn)實是沒法直接通過context.WithValue(w.Context(), "greet", "Hello")
設置值的,因為Worker
接口僅返回Context
并重新沒有設置Context
方法提供;由于Worker
是第三方的接口,不可直接去改的。因此就需要擴展接口。問題如圖:
如何解決這個問題呢?現(xiàn)在要解決什么問題?如果把問題細分的話,要解決2個問題:
- 擴展一個設置Context的方法
- 保留原來的方法,只做擴充,不做刪除 那要解決這個問題最好的方式就是接口嵌套。
代碼該如何寫呢?解法如下:
首先是要定義新的接口wrapWorker,將舊的接口嵌套進來,這樣就保留了原始有的方法,然后在定義新的方法SetContext(context.Context)
,接口定義好了,就用實現(xiàn)接口對吧,思路很直接。
緊接著定義一個 wrap
結構體, 保存ctx,同時實SetContext(context.Context)
還有重要的一環(huán)節(jié),就是重寫 Context() context.Context
方法。
實現(xiàn)代碼如下:
// 第三方的接口定義 type Worker interface { Context() context.Context DoSomeThing() // ... } type wrapWorker interface { Worker SetContext(context.Context) } type wrap struct { Worker ctx context.Context } func newWrap(w Worker) wrapWorker { return &wrap{ w, w.Context(), } } func (wp *wrap) SetContext(ctx context.Context) { wp.ctx = ctx } func (wp wrap) Context() context.Context { return wp.ctx } type contextKey string func work(w Worker) { wp := newWrap(w) ctx := context.WithValue(w.Context(), contextKey("greet"), "Hello") wp.SetContext(ctx) next(wp) } func next(w Worker) { v := w.Context().Value(contextKey("greet")) fmt.Printf("-> %v \n", v) w.DoSomeThing() } type person string func (person) Context() context.Context { return context.Background() } func (p person) DoSomeThing() { fmt.Println(string(p), "吃飯睡覺打代碼~") } func main() { var p person = "張三" work(p) }
執(zhí)行結果符合預期,OK 沒問題~
其實上面不定義新接口wrapWorker
也是可以的,用wrap
包裹接口即可。用接口設計更符合接口編程思想。
到此這篇關于詳解Go語言如何對數(shù)據(jù)庫進行CRUD操作的文章就介紹到這了,更多相關Go語言數(shù)據(jù)庫CRUD操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
golang中channel+error來做異步錯誤處理有多香
官方推薦golang中錯誤處理當做值處理, 既然是值那就可以在channel中傳輸,這篇文章主要介紹了golang 錯誤處理channel+error真的香,需要的朋友可以參考下2023-01-01Go語言常見錯誤之濫用getters/setters誤區(qū)實例探究
在Go語言編程中,恰如其分地使用getters和setters是至關重要的,過度和不適當?shù)厥褂盟鼈兛赡軐е麓a冗余、可讀性差和封裝不當,在本文中,我們將深入探討如何識別濫用getter和setter的情況,以及如何采取最佳實踐來避免這些常見的Go錯誤2024-01-01