Singleton pattern là gì?

Singleton là một design pattern trong số 5 design pattern thuộc nhóm Creational Design Pattern bao gồm : Factory method, Abstract Factory, Builder, Prototype, Singleton.

Single Pattern đảm bảo rằng một class chỉ có duy nhất một instance, và cung cấp một cách toàn cầu để truy cấp tới instance đó.

hình ảnh minh họa singleton pattern

Tại sao cần dùng Singleton Pattern

Đa phần các đối tượng trong một ứng dụng đều chịu trách nhiệm cho công việc của chúng, truy xuất dữ liệu tự lưu trữ (self-contained data) và các tham chiếu trong phạm vi của chúng.

Tuy nhiên, nhiều đối tượng có thêm những nhiệm vụ và có ảnh hưởng của nó rộng hơn. Chẳng hạn như quản lý các nguồn tài nguyên bị giới hạn hay là theo dõi toàn bộ trạng thái của hệ thống.

Singleton dùng để làm gì?

  • Đảm bảo rằng 1 class chỉ có 1 instance duy nhất và class này luôn sẵn sàng để sử dụng ở bất kỳ thời điểm hoặc vị trí nào trong phần mềm ứng dụng của chúng ta.

  • Việc quản lý việc truy cập tốt hơn vì chỉ có một thể hiện duy nhất.

  • Có thể quản lý số lượng thể hiện của một lớp trong giới hạn chỉ định.

Cài đặt Singleton Pattern trong golang

Trong Golang, có một số cách giúp chúng ta có thể cài đặt Singleton Pattern. Có thể kể tới như:

  • Sử dụng func init()
  • Sử dụng sync.Once
  • Sử dụng hàm khởi tạo
  • Sử dụng Mutex locks
  • ...

Bây giờ chúng ta cùng đi vào các ví dụ demo để cài đặt Singleton Pattern theo các cách ở trên

Sử dụng func init()

package singleton

type singleton struct {}

var instance *singleton
func init() {
    singleton = &singleton{}
}

func GetInstance() *singleton {
    return singleton
}

Hàm init() trong package đảm bảo được gọi một lần từ một luồng duy nhất (sử dụng cách này giúp chúng ta thread safe trừ khi chúng ta sử dụng multi-threaded).

Nhưng nó lại khiến chúng ta phụ thuộc vào thứ tự khởi chạy, và chúng ta không nên viết code vào trong hàm init() mà chúng ta cần đảm bảo code được thực thi tại bất kỳ thời điểm nào

Sử dụng sync.Once

package singleton

import (
    "sync"
)

type singleton struct {}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

Với cách này chúng ta sẽ sử dụng 1 build-in package của golang là async. Phương thức Do giúp chúng ta thực hiện 1 function 1 lần duy nhất, điều này đảm bảo thread safe chúng ta sử dụng multi-threaded.

Sử dụng hàm khởi tạo

Nếu bạn đã biết một ngôn ngữ khác và chuyển qua lập trình golang thì đây là cách phổ biến nhất để triển khai singleton pattern.

package singleton

type singleton struct {}

var instance *singleton

func GetInstance() *singleton {
    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

Sử dụng Mutex locks

package singleton

import (
    "sync"
)

type singleton struct {}

var instance *singleton
var mu sync.Mutex

func GetInstance() *singleton {
    mu.Lock()
    defer mu.Unlock()

    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

Ở đoạn code trên, ngay trong phần định nghĩa hàm, chúng ta sử dụng 2 câu lệnh

mu.Lock()
defer mu.Unlock()

2 câu lệnh này đảm bảo rằng chỉ có một goroutine có thể thực thi mã này

Tuy nhiên, một câu hỏi đặt ra là chúng ta có cần Lock và Unlock mỗi khi hàm GetInstance() được gọi hay không khi khối lệnh if chỉ được thực thi 1 lần duy nhất trong toàn bộ vòng đời. Khi một instance được tạo ra, chúng ta không cần phải Lock hay Unlock nữa. Vì vậy với cách sử dụng mutex lock, chúng ta đã tạo ra một nút thắt cổ chai không cần thiết trong toàn bộ chương trình.