詳解Go語言如何對(duì)數(shù)據(jù)庫進(jìn)行CRUD操作
Go語言中的接口
在這里我將不會(huì)介紹接口基礎(chǔ)知識(shí),如:接口定義和實(shí)現(xiàn)、空接口、類型斷言、v, ok := i.(T)、switch x.(type)、接口嵌套、指針接收器與值接收器實(shí)現(xiàn)接口、接口零值 這些基礎(chǔ)概念不作多說,如不太清楚上面接口知識(shí),自行學(xué)習(xí)補(bǔ)充。這里介紹在開發(fā)中接口實(shí)在用法,也是由上面基礎(chǔ)演變而成。
使用接口編程
有這么一個(gè)普通場景,上傳并保存文件,三七二十一蹭蹭蹭,一下子搞完,so easy,代碼如下:

項(xiàng)目準(zhǔn)備上線,公司購買了OSS存儲(chǔ),文件資源要存到OSS上,那么如何修改上面的代碼呢? 這個(gè)時(shí)候接口就可以上場了。 實(shí)現(xiàn)如下:
// NOTE: storage.go
// 存儲(chǔ)文件接口
type Storage interface {
Save(data []byte) (string, error)
}
// 開發(fā)時(shí)候使用本地存儲(chǔ)
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存儲(chǔ)
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 都是實(shí)現(xiàn)了接口,而 saveFile(store Storage) 接受指針。 這種方式就是接受接口返回結(jié)構(gòu),是 Go 語言接口編程中非常經(jīng)典實(shí)用模式。
接受接口返回結(jié)構(gòu)
上面已經(jīng)的例子就是使用這個(gè)法則,使用這個(gè)法則讓代碼變得更加靈活,它是松耦合的,更加方便mock數(shù)據(jù)測試,可以避免一些不必要的bug。
現(xiàn)在舉一個(gè)不方便測試的例子:
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() {
// 假設(shè)是真實(shí)的鏈接 &sql.DB{}
srv := NewService(&sql.DB{})
srv.Repo.Insert()
srv.Repo.Update()
}上面的例子看著無任何問題?。窟@不很正常。是的很正常,就是測試的時(shí)候不方便。 如果要測試起來,你必須要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)庫,連接數(shù)據(jù)庫,創(chuàng)建sql.DB實(shí)例。才能完成測試。如果想簡單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() {
// 假設(shè)是真實(shí)的鏈接 &sql.DB{}
r := NewRepository(&sql.DB{})
srv := NewService(r)
srv.Repo.Insert()
srv.Repo.Update()
}
接口檢查
上面的例子,func NewRepository(db *sql.DB) *repository 返回的是結(jié)構(gòu)體指針。在編碼過程,很容易忘記實(shí)現(xiàn)接口,甚至全部接口都沒實(shí)現(xiàn),編譯器也不會(huì)報(bào)錯(cuò)。我怎么知道repository結(jié)構(gòu)體就一定全部實(shí)現(xiàn)了Repository接口呢,對(duì)吧。
那這個(gè)時(shí)候就要用到接口檢查了。 接口檢查的語法是
var _ MyInterface = (*MyStruct)(nil)

上面當(dāng)我沒有實(shí)現(xiàn)Inser方法時(shí)就會(huì)直接編譯不通過。
這就是接口檢查帶來好處。
擴(kuò)展接口
假設(shè)有這么一場景,在開發(fā)中我們使用到了第三方庫,第三庫提供一個(gè)Worker接口,我們?cè)?code>work、next兩個(gè)方法都使用到Worker接口,入?yún)⒕褪撬?。這個(gè)時(shí)候我們希望在work函數(shù)中對(duì)Context增加額外的屬性,提供給next函數(shù)使用。但現(xiàn)實(shí)是沒法直接通過context.WithValue(w.Context(), "greet", "Hello")設(shè)置值的,因?yàn)?code>Worker接口僅返回Context并重新沒有設(shè)置Context方法提供;由于Worker是第三方的接口,不可直接去改的。因此就需要擴(kuò)展接口。問題如圖:

如何解決這個(gè)問題呢?現(xiàn)在要解決什么問題?如果把問題細(xì)分的話,要解決2個(gè)問題:
- 擴(kuò)展一個(gè)設(shè)置Context的方法
- 保留原來的方法,只做擴(kuò)充,不做刪除 那要解決這個(gè)問題最好的方式就是接口嵌套。
代碼該如何寫呢?解法如下:
首先是要定義新的接口wrapWorker,將舊的接口嵌套進(jìn)來,這樣就保留了原始有的方法,然后在定義新的方法SetContext(context.Context),接口定義好了,就用實(shí)現(xiàn)接口對(duì)吧,思路很直接。
緊接著定義一個(gè) wrap 結(jié)構(gòu)體, 保存ctx,同時(shí)實(shí)SetContext(context.Context)
還有重要的一環(huán)節(jié),就是重寫 Context() context.Context 方法。
實(shí)現(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í)行結(jié)果符合預(yù)期,OK 沒問題~

其實(shí)上面不定義新接口wrapWorker也是可以的,用wrap包裹接口即可。用接口設(shè)計(jì)更符合接口編程思想。
到此這篇關(guān)于詳解Go語言如何對(duì)數(shù)據(jù)庫進(jìn)行CRUD操作的文章就介紹到這了,更多相關(guān)Go語言數(shù)據(jù)庫CRUD操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang defer延遲語句的實(shí)現(xiàn)
defer擁有注冊(cè)延遲調(diào)用的機(jī)制,本文主要介紹了Golang defer延遲語句的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
golang中channel+error來做異步錯(cuò)誤處理有多香
官方推薦golang中錯(cuò)誤處理當(dāng)做值處理, 既然是值那就可以在channel中傳輸,這篇文章主要介紹了golang 錯(cuò)誤處理channel+error真的香,需要的朋友可以參考下2023-01-01
Go語言常見錯(cuò)誤之濫用getters/setters誤區(qū)實(shí)例探究
在Go語言編程中,恰如其分地使用getters和setters是至關(guān)重要的,過度和不適當(dāng)?shù)厥褂盟鼈兛赡軐?dǎo)致代碼冗余、可讀性差和封裝不當(dāng),在本文中,我們將深入探討如何識(shí)別濫用getter和setter的情況,以及如何采取最佳實(shí)踐來避免這些常見的Go錯(cuò)誤2024-01-01
Golang 探索對(duì)Goroutine的控制方法(詳解)
下面小編就為大家分享一篇Golang 探索對(duì)Goroutine的控制方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12
Golang中context庫的高級(jí)應(yīng)用
context庫不僅對(duì)于提升代碼的效率和性能至關(guān)重要,而且還幫助開發(fā)者在復(fù)雜的系統(tǒng)中保持代碼的清晰和可維護(hù)性,下面我們就來看看context庫的高級(jí)應(yīng)用吧2024-01-01

