首頁 > 軟體

詳解Go語言設計模式之單例模式

2022-10-23 18:01:15

單例模式的概念

單例模式很容易記住。就像名稱一樣,它只能提供物件的單一範例,保證一個類只有一個範例,並提供一個全域性存取該範例的方法。

在第一次呼叫該範例時被建立,然後在應用程式中需要使用該特定行為的所有部分之間重複使用。

單例模式結構

單例模式的使用場景

你會在許多不同的情況下使用單例模式。比如:

  • 當你想使用同一個資料庫連線來進行每次查詢時
  • 當你開啟一個安全 Shell(SSH)連線到一個伺服器來做一些任務時。 而不想為每個任務重新開啟連線
  • 如果你需要限制對某些變數或空間的存取,你可以使用一個單例作為 作為這個變數的門(在 Go 中使用通道可以很好地實現)
  • 如果你需要限制對某些空間的呼叫數量,你可以建立一個單例範例使得這種呼叫只在可接受的視窗中進行

單例模式還有跟多的用途,這裡只是簡單的舉出一些。

單例模式例子:特殊的計數器

我們可以寫一個計數器,它的功能是用於儲存它在程式執行期間被呼叫的次數。這個計數器的需要滿足的幾個要求:

  • 當之前沒有建立過計數器 count 時,將建立一個新的計數器 count = 0
  • 如果已經建立了一個計數器,則返回此範例實際儲存的 count
  • 如果我們呼叫方法 AddOne 一次,計數 count 必須增加 1

在這個場景下,我們需要有 3 個測試來堅持我們的單元測試。

第一個單元測試

與 Java 或 C++ 這種物件導向語言中不同,Go 實現單例模式沒有像靜態成員的東西(通過 static 修飾),但是可以通過包的範圍來提供一個類似的功能。

首先,我們要為單例物件編寫包的宣告:

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
}

然後,我們通過編寫測試程式碼來驗證我們宣告的函數:

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 %dn", 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 %dn", currentCount)
	}
}

第一個測試是檢查是顯而易見,但在複雜的應用中,其重要性也不小。當我們要求獲得一個計數器的範例時,我們實際上需要得到一個結果。

我們把物件的建立委託給一個未知的包,而這個物件在建立或檢索物件時可能失敗。我們還將當前的計數器儲存在變數 expectedCounter 中,以便以後進行比較。即:

	currentCount := count.AddOne()
	if currentCount != 1 {
		t.Errorf("After calling for the first time to count, the count must be 1 but it is %dn", currentCount)

	}

執行上面的程式碼:

$ 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

單例模式實現

最後,我們必須實現單例模式。正如我們前面提到的,通常做法是寫一個靜態方法和範例來檢索單例模式範例。

在 Go 中,沒有 static 這個關鍵字,但是我們可以通過使用包的範圍來達到同樣的效果。

首先,我們建立一個結構體,其中包含我們想要保證的物件 在程式執行過程中成為單例的物件。

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++ 語言中,變數範例會在程式開始時被初始化為 NULL。 但在 Go 中,你可以將結構的指標初始化為 nil,但不能將一個結構初始化為 nil (相當於其他語言的 NULL)。

所以 var instance *singleton* 這一語句定義了一個指向結構的指標為 nil ,而變數稱為 instance

我們建立了一個 GetInstance 方法,檢查範例是否已經被初始化(instance == nil),並在已經分配的空間中建立一個範例 instance = new(singleton)

Addone() 方法將獲取變數範例的計數,並逐個加 1,然後返回當前計數器的值。

再一次執行單元測試程式碼:

$ go test -v -run=GetInstance .
=== RUN   TestGetInstance
--- PASS: TestGetInstance (0.00s)
PASS
ok      github.com/yuzhoustayhungry/GoDesignPattern/singleton   0.297s

單例模式優缺點

優點:

  • 你可以保證一個類只有一個範例。
  • 你獲得了一個指向該範例的全域性存取節點。
  • 僅在首次請求單例物件時對其進行初始化。

缺點:

  • 違反了單一職責原則。 該模式同時解決了兩個問題。
  • 單例模式可能掩蓋不良設計, 比如程式各元件之間相互瞭解過多等。
  • 該模式在多執行緒環境下需要進行特殊處理, 避免多個執行緒多次建立單例物件。
  • 單例的使用者端程式碼單元測試可能會比較困難, 因為許多測試框架以基於繼承的方式建立模擬物件。 由於單例類別建構函式是私有的, 而且絕大部分語言無法重寫靜態方法, 所以你需要想出仔細考慮模擬單例的方法。 要麼乾脆不編寫測試程式碼, 或者不使用單例模式。

以上就是詳解Go語言設計模式之單例模式的詳細內容,更多關於Go語言 單例模式的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com