詳解Go語言設(shè)計(jì)模式之單例模式
單例模式的概念
單例模式很容易記住。就像名稱一樣,它只能提供對(duì)象的單一實(shí)例,保證一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問該實(shí)例的方法。
在第一次調(diào)用該實(shí)例時(shí)被創(chuàng)建,然后在應(yīng)用程序中需要使用該特定行為的所有部分之間重復(fù)使用。
單例模式結(jié)構(gòu)
單例模式的使用場(chǎng)景
你會(huì)在許多不同的情況下使用單例模式。比如:
- 當(dāng)你想使用同一個(gè)數(shù)據(jù)庫連接來進(jìn)行每次查詢時(shí)
- 當(dāng)你打開一個(gè)安全 Shell(SSH)連接到一個(gè)服務(wù)器來做一些任務(wù)時(shí)。 而不想為每個(gè)任務(wù)重新打開連接
- 如果你需要限制對(duì)某些變量或空間的訪問,你可以使用一個(gè)單例作為 作為這個(gè)變量的門(在 Go 中使用通道可以很好地實(shí)現(xiàn))
- 如果你需要限制對(duì)某些空間的調(diào)用數(shù)量,你可以創(chuàng)建一個(gè)單例實(shí)例使得這種調(diào)用只在可接受的窗口中進(jìn)行
單例模式還有跟多的用途,這里只是簡(jiǎn)單的舉出一些。
單例模式例子:特殊的計(jì)數(shù)器
我們可以寫一個(gè)計(jì)數(shù)器,它的功能是用于保存它在程序執(zhí)行期間被調(diào)用的次數(shù)。這個(gè)計(jì)數(shù)器的需要滿足的幾個(gè)要求:
- 當(dāng)之前沒有創(chuàng)建過計(jì)數(shù)器
count
時(shí),將創(chuàng)建一個(gè)新的計(jì)數(shù)器count = 0
- 如果已經(jīng)創(chuàng)建了一個(gè)計(jì)數(shù)器,則返回此實(shí)例實(shí)際保存的
count
數(shù) - 如果我們調(diào)用方法
AddOne
一次,計(jì)數(shù)count
必須增加 1
在這個(gè)場(chǎng)景下,我們需要有 3 個(gè)測(cè)試來堅(jiān)持我們的單元測(cè)試。
第一個(gè)單元測(cè)試
與 Java 或 C++ 這種面向?qū)ο笳Z言中不同,Go 實(shí)現(xiàn)單例模式?jīng)]有像靜態(tài)成員的東西(通過 static 修飾),但是可以通過包的范圍來提供一個(gè)類似的功能。
首先,我們要為單例對(duì)象編寫包的聲明:
package singleton type Singleton struct { count int } var instance *Singleton func init() { instance = &Singleton{} } func GetInstance() *Singleton { return nil } func (s *Singleton) AddOne() int { return 0 }
然后,我們通過編寫測(cè)試代碼來驗(yàn)證我們聲明的函數(shù):
package singleton import ( "testing" ) func TestGetInstance(t *testing.T) { count := GetInstance() if count == nil { t.Error("A new connection object must have been made") } expectedCounter := count currentCount := count.AddOne() if currentCount != 1 { t.Errorf("After calling for the first time to count, the count must be 1 but it is %d\n", currentCount) } count2 := GetInstance() if count2 != expectedCounter { t.Error("Singleton instances must be different") } currentCount = count2.AddOne() if currentCount != 2 { t.Errorf("After calling 'AddOne' using the second counter, the current count must be 2 but was %d\n", currentCount) } }
第一個(gè)測(cè)試是檢查是顯而易見,但在復(fù)雜的應(yīng)用中,其重要性也不小。當(dāng)我們要求獲得一個(gè)計(jì)數(shù)器的實(shí)例時(shí),我們實(shí)際上需要得到一個(gè)結(jié)果。
我們把對(duì)象的創(chuàng)建委托給一個(gè)未知的包,而這個(gè)對(duì)象在創(chuàng)建或檢索對(duì)象時(shí)可能失敗。我們還將當(dāng)前的計(jì)數(shù)器存儲(chǔ)在變量 expectedCounter
中,以便以后進(jìn)行比較。即:
currentCount := count.AddOne() if currentCount != 1 { t.Errorf("After calling for the first time to count, the count must be 1 but it is %d\n", currentCount) }
運(yùn)行上面的代碼:
$ go test -v -run=GetInstance . === RUN TestGetInstance singleton_test.go:12: A new connection object must have been made singleton_test.go:19: After calling for the first time to count, the count must be 1 but it is 0 singleton_test.go:31: After calling 'AddOne' using the second counter, the current count must be 2 but was 0 --- FAIL: TestGetInstance (0.00s) FAIL FAIL github.com/yuzhoustayhungry/GoDesignPattern/singleton 0.412s FAIL
單例模式實(shí)現(xiàn)
最后,我們必須實(shí)現(xiàn)單例模式。正如我們前面提到的,通常做法是寫一個(gè)靜態(tài)方法和實(shí)例來檢索單例模式實(shí)例。
在 Go 中,沒有 static
這個(gè)關(guān)鍵字,但是我們可以通過使用包的范圍來達(dá)到同樣的效果。
首先,我們創(chuàng)建一個(gè)結(jié)構(gòu)體,其中包含我們想要保證的對(duì)象 在程序執(zhí)行過程中成為單例的對(duì)象。
package singleton type Singleton struct { count int } var instance *Singleton func init() { instance = &Singleton{} } func GetInstance() *Singleton { if instance == nil { instance = new(Singleton) } return instance } func (s *Singleton) AddOne() int { s.count++ return s.count }
我們來分析一下這段代碼的差別,在 Java 或 C++ 語言中,變量實(shí)例會(huì)在程序開始時(shí)被初始化為 NULL
。 但在 Go 中,你可以將結(jié)構(gòu)的指針初始化為 nil
,但不能將一個(gè)結(jié)構(gòu)初始化為 nil
(相當(dāng)于其他語言的 NULL
)。
所以 var instance *singleton*
這一語句定義了一個(gè)指向結(jié)構(gòu)的指針為 nil
,而變量稱為 instance
。
我們創(chuàng)建了一個(gè) GetInstance
方法,檢查實(shí)例是否已經(jīng)被初始化(instance == nil
),并在已經(jīng)分配的空間中創(chuàng)建一個(gè)實(shí)例 instance = new(singleton)
。
Addone()
方法將獲取變量實(shí)例的計(jì)數(shù),并逐個(gè)加 1,然后返回當(dāng)前計(jì)數(shù)器的值。
再一次運(yùn)行單元測(cè)試代碼:
$ go test -v -run=GetInstance . === RUN TestGetInstance --- PASS: TestGetInstance (0.00s) PASS ok github.com/yuzhoustayhungry/GoDesignPattern/singleton 0.297s
單例模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 你可以保證一個(gè)類只有一個(gè)實(shí)例。
- 你獲得了一個(gè)指向該實(shí)例的全局訪問節(jié)點(diǎn)。
- 僅在首次請(qǐng)求單例對(duì)象時(shí)對(duì)其進(jìn)行初始化。
缺點(diǎn):
- 違反了單一職責(zé)原則。 該模式同時(shí)解決了兩個(gè)問題。
- 單例模式可能掩蓋不良設(shè)計(jì), 比如程序各組件之間相互了解過多等。
- 該模式在多線程環(huán)境下需要進(jìn)行特殊處理, 避免多個(gè)線程多次創(chuàng)建單例對(duì)象。
- 單例的客戶端代碼單元測(cè)試可能會(huì)比較困難, 因?yàn)樵S多測(cè)試框架以基于繼承的方式創(chuàng)建模擬對(duì)象。 由于單例類的構(gòu)造函數(shù)是私有的, 而且絕大部分語言無法重寫靜態(tài)方法, 所以你需要想出仔細(xì)考慮模擬單例的方法。 要么干脆不編寫測(cè)試代碼, 或者不使用單例模式。
以上就是詳解Go語言設(shè)計(jì)模式之單例模式的詳細(xì)內(nèi)容,更多關(guān)于Go語言 單例模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang使用Gin框架實(shí)現(xiàn)路由分類處理請(qǐng)求流程詳解
Gin是一個(gè)golang的微框架,封裝比較優(yōu)雅,具有快速靈活,容錯(cuò)方便等特點(diǎn),這篇文章主要介紹了Golang使用Gin框架實(shí)現(xiàn)路由分類處理請(qǐng)求,感興趣的同學(xué)可以參考下文2023-05-05Go?for-range?的?value值地址每次都一樣的原因解析
循環(huán)語句是一種常用的控制結(jié)構(gòu),在?Go?語言中,除了?for?關(guān)鍵字以外,還有一個(gè)?range?關(guān)鍵字,可以使用?for-range?循環(huán)迭代數(shù)組、切片、字符串、map?和?channel?這些數(shù)據(jù)類型,這篇文章主要介紹了Go?for-range?的?value值地址每次都一樣的原因解析,需要的朋友可以參考下2023-05-05go如何優(yōu)雅關(guān)閉Graceful?Shutdown服務(wù)
這篇文章主要為大家介紹了go優(yōu)雅關(guān)閉Graceful?Shutdown服務(wù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Go語言dolphinscheduler任務(wù)調(diào)度處理
這篇文章主要為大家介紹了Go語言dolphinscheduler任務(wù)調(diào)度處理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06