Bỏ qua để đến nội dung

Dependency Injection cơ bản

Dependency Injection (DI) — nói đơn giản là cách truyền dependency từ bên ngoài vào thay vì để class tự tạo.

Chương này giải thích tại sao DI quan trọng và khi nào nên (hoặc không nên) dùng Singleton.

Có hai trường phái chính:

Code thuần (Vanilla Code):

  • Tự viết code để truyền dependency qua constructor.
  • Ưu điểm: Rõ ràng, dễ debug, ai đọc cũng hiểu.
  • Nhược điểm: Phải viết thêm boilerplate code.

Dùng DI Framework (Swinject, Koin, Hilt…)

  • Ưu điểm: Giảm boilerplate, tự động resolve dependency.
  • Nhược điểm: Magic ẩn sau annotation, khó debug khi sai.

Cuốn sách này ủng hộ code thuần. Đơn giản > phép thuật.

Xem xét ví dụ:

class CourseAPI {
private let tutorAPI = TutorAPI() // Tự tạo — tight coupling
private let todoList = TODOList() // Tự tạo — tight coupling
}

Vấn đề: CourseAPI bị tight coupling với TutorAPITODOList cụ thể. Không thể:

  • Thay bằng mock khi test
  • Đổi implementation (staging vs. production)

Giải pháp — DI:

class CourseAPI {
private let tutorAPI: TutorAPI
private let todoList: TODOList
init(tutorAPI: TutorAPI, todoList: TODOList) {
self.tutorAPI = tutorAPI
self.todoList = todoList
}
}

Bây giờ ta truyền dependency từ ngoài vào → dễ test, dễ thay đổi.

Singleton là cách dễ nhất để tránh truyền dependency:

class TutorAPI {
let api = API.shared // Global access
}

Tuy tiện, Singleton gây ra nhiều vấn đề:

Singleton là global state. Nhiều thread cùng đọc/ghi → race condition.

Ví dụ thực tế: Đang chuyển tiền thì user logout rồi login tài khoản khác. Nếu User là singleton, tiền có thể bị ghi nhầm tài khoản.

Muốn tách CourseAPI ra module riêng, nhưng nó dùng User.shared (singleton ở module App). Kết quả: circular dependency — không tách ra được.

“Cái này chỉ cần 1 instance” — cho đến khi product manager nói “Hỗ trợ multi-account”. Lúc đó refactor singleton thành DI rất tốn công.

Singleton không hoàn toàn xấu. Phù hợp cho:

  • Logger: Write-only, ít rủi ro conflict.
  • Database/Cache coordinator: Đảm bảo single access point tránh data corruption.

Kết luận: Mặc định dùng DI (truyền dependency qua constructor). Chỉ dùng Singleton khi có lý do rõ ràng. Code thuần > DI framework cho hầu hết trường hợp.