Singleton và 1001 câu hỏi
Khái niệm Singleton và Lý do Tại Sao Nó Có Ích
Bắt nguồn từ thực tế, một số lớp như quản lý bộ nhớ cache, quản lý cơ sở dữ liệu, và thông báo thường xuyên được sử dụng trong nhiều lớp khác nhau. Điều này dẫn đến việc một thể hiện của cùng một lớp được tạo ra và sử dụng tại nhiều điểm khác nhau trong mã nguồn.
Kết quả là sự trùng lặp mã nguồn, khó quản lý, và có thể dẫn đến chi phí cao khi thay đổi mã nguồn.
Hãy tưởng tượng rằng ứng dụng của tôi là một ứng dụng web (web-app), và trong môi trường này, việc quản lý các cookies là một phần quan trọng không thể thiếu. Tôi có thể triển khai nó bằng cách sau:
class CookiesManager {
func saveCookies() {
// do stuff
}
func loadCookies() {
// do stuff
}
func refreshCookies() {
// do stuff
}
}
class ViewControllerA: UIViewController {
override func viewDidLoad() {
let cookiesManager = CookiesManager()
cookiesManager.loadCookies()
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
let cookiesManager = CookiesManager()
cookiesManager.refreshCookies()
}
}
Tuy nhiên, đoạn mã trên cũng đặt ra một vấn đề: Quản lý cookies cần phải diễn ra trong suốt thời gian chạy của ứng dụng, điều này có nghĩa là lớp CookiesManager có thể phải được khởi tạo tại nhiều điểm khác nhau trong mã nguồn.
Vấn đề này có thể được giải quyết bằng cách duy nhất hóa (singleton) lớp CookiesManager, tức là chỉ cho phép tồn tại một instance của nó. Vì vậy, theo lý thuyết, Singleton Pattern là một giải pháp hợp lý để loại bỏ vấn đề này.
Viết Singleton như thế nào để đảm bảo tính đúng đắn?
Đầu tiên, khi tiếp xúc với Design Pattern, bước đầu tiên thường bao gồm việc xem xét UML Diagram của nó:
Chuyển đổi UML thành mã nguồn:
class Singleton {
init() {}
private static var instance = Singleton()
public static func getInstance() -> Singleton {
return instance
}
}
Mã nguồn rất đơn giản. Để sử dụng một instance của Singleton, bạn chỉ cần gọi Singleton.getInstance() và bạn đã có thể sử dụng nó. Cách triển khai này khá phổ biến, đặc biệt trong các ngôn ngữ như Java, C#,…
Tuy nhiên, Swift là một ngôn ngữ hiện đại và thông minh. Với Swift, chúng ta có cách triển khai đơn giản và hiệu quả hơn:
class Singleton {
static let shared: Singleton = Singleton()
}
Phần đơn giản thì dễ thấy, nhưng tại sao lại nói nó hiệu quả hơn?
Trong Swift, là một ngôn ngữ tĩnh (static), điều này đồng nghĩa với việc biến, hàm, và lớp được đánh dấu là static sẽ được khởi tạo tại thời điểm biên dịch (compile time).
Tuy nhiên, đối với các hằng số toàn cầu (global constants) hoặc biến toàn cầu (global variables), Swift mặc định sử dụng cơ chế khởi tạo lười biếng (lazy initialization), nghĩa là biến chỉ được khởi tạo khi nó thực sự cần được sử dụng.
Bạn có thể tham khảo thêm thông tin về cơ chế này tại liên kết sau. Nếu bạn sử dụng cú pháp static let, Swift hiểu rằng đó là biến toàn cầu, và do đó biến shared sẽ chỉ được khởi tạo khi nó cần thiết (là một ví dụ về cơ chế khởi tạo lười biếng, một mẫu thiết kế khác, sẽ được nói đến sau).
UML của Singleton lúc này sẽ được chuyển đổi:
class CookiesManager {
static let shared = CookiesManager()
func saveCookies() {
// do stuff
}
func loadCookies() {
// do stuff
}
func refreshCookies() {
// do stuff
}
}
class ViewControllerA: UIViewController {
override func viewDidLoad() {
CookiesManager.shared.saveCookies()
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
CookiesManager.shared.refreshCookies()
}
}
Thực tế, bạn có thể dễ dàng thấy mẫu Singleton Pattern được Apple sử dụng trong một số module phổ biến:
UIApplication.shared
NotificationCenter.default
UserDefaults.standard
Tổng kết
Tóm lại, trong bài viết này, chúng ta đã thảo luận về Singleton Design Pattern, một mẫu thiết kế đơn giản nhưng rất phổ biến trong lập trình hướng đối tượng. Chúng ta đã cùng trả lời các câu hỏi sau:
- Singleton là gì và tại sao chúng ta cần nó?
- Làm thế nào để triển khai Singleton đúng cách?
- Phân tích chi tiết về Singleton.
Tuy Singleton có lợi ích thực sự và tiện ích, nhưng cũng có thể trở thành một Anti-pattern nếu sử dụng không đúng cách. Trước khi áp dụng Singleton, hãy xem xét kỹ về vấn đề liên quan đến việc chia sẻ trạng thái toàn cục (Global share state) và đảm bảo rằng Singleton thực sự là lựa chọn phù hợp cho tình huống cụ thể của bạn.
Tham khảo nguồn: