Menu
CoddyTech

Mẫu Singleton

Thuộc phần Lập trình hướng đối tượng trong hành trình Swift của Coddy. Bài học 42 trên 57.

Mẫu thiết kế là giải pháp đã được kiểm chứng cho một vấn đề thiết kế phổ biến. Mẫu singleton đảm bảo một lớp chỉ có đúng một đối tượng, có thể truy cập từ bất cứ đâu. Trong Swift, mẫu này dùng static let để lưu đối tượng, cùng với private init để không ai có thể tạo thêm đối tượng khác:

final class Settings {
    static let shared = Settings()
    var theme = "light"

    private init() {}
}

Settings.shared.theme = "dark"
print(Settings.shared.theme)

Đầu ra:

dark

Mỗi lần dùng Settings.shared đều tham chiếu đến cùng một đối tượng, vì vậy thay đổi ở một phần của chương trình sẽ hiển thị ở mọi nơi. static let chỉ được tạo ở lần dùng đầu tiên và vẫn an toàn ngay cả khi nhiều luồng bắt đầu cùng lúc:

final class Counter {
    static let shared = Counter()
    private(set) var hits = 0
    private init() {}
    func hit() { hits += 1 }
}

func pageA() { Counter.shared.hit() }
func pageB() { Counter.shared.hit(); Counter.shared.hit() }
pageA()
pageB()
print(Counter.shared.hits)
print(Counter.shared === Counter.shared)

Đầu ra:

3
true

Vì hàm khởi tạo là riêng tư, Settings() không thể biên dịch bên ngoài lớp, nhờ đó đảm bảo chỉ có một đối tượng:

// let other = Settings()   // lỗi: không thể truy cập hàm khởi tạo 'Settings' vì mức bảo vệ 'private'

Hãy dùng singleton một cách tiết chế. Singleton là trạng thái toàn cục: bất kỳ đoạn code nào cũng có thể thay đổi nó, và các bài test khó bắt đầu với một bản sao sạch. Singleton phù hợp với những thứ thực sự chỉ có một trong mỗi chương trình, chẳng hạn như trình ghi nhật ký hoặc cấu hình của ứng dụng; truyền một đối tượng làm tham số thường rõ ràng hơn.

// Dễ kiểm thử hơn: logger được truyền vào
func process(order: Order, log: Logger) { log.write("processing \(order.id)") }
challenge icon

Thử thách

Dễ

Hoàn thiện singleton Logger. Lớp này có một instance static let shared, hàm khởi tạo riêng tư, một mảng entries với private(set), và log(_:level:), dùng để lưu [INFO] message hoặc [ERROR] message (level là info hoặc error). errorCount là thuộc tính được tính toán. Code được cung cấp gọi Logger.shared từ hai hàm khác nhau, vì vậy cả hai phải truy cập cùng một instance.

Code được cung cấp đọc các dòng info,message hoặc error,message, chuyển các dòng info qua handleInfo và các dòng error qua handleError, sau đó in ra mọi mục và errors: 1.

Code của bạn nằm trong Logger.swift và Functions.swift. main.swift chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

// Đoạn code đầu vào/đầu ra đã cung cấp: giữ nguyên
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let p = line.split(separator: ",", maxSplits: 1).map(String.init)
    if p[0] == "error" { handleError(p[1]) } else { handleInfo(p[1]) }
}
for e in Logger.shared.entries { print(e) }
print("errors: \(Logger.shared.errorCount)")
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Swift