1. Giới thiệu
Bạn đã bao giờ lắp xếp domino trong nhiều giờ chỉ để một người bạn vô tình chạm vào viên đầu tiên, khiến cả dãy đổ sập chưa? Chúc mừng! Bạn vừa trải nghiệm Tight Coupling trong thực tế – nơi một thay đổi nhỏ có thể làm sụp đổ toàn bộ hệ thống.
Trong thế giới phát triển iOS, Tight Coupling giống như việc cố nhét cả một ngôi nhà vào trong một chiếc vali: mọi thứ dính chặt với nhau, và một thay đổi có thể gây ra sự hỗn loạn. Ngược lại, nếu là Loose Coupling, bạn giống như việc “sắp xếp mọi thứ vào từng ngăn kéo riêng” – vẫn hoạt động cùng nhau mà không cần phải chạm vào nhau, nhằm tránh rối loạn.
Bài viết này cung cấp cho bạn cái nhìn rõ ràng về sự khác biệt giữa Tight Coupling và Loose Coupling, tác động của chúng trong phát triển iOS, cũng như cách áp dụng các kỹ thuật và mẫu thiết kế để giảm thiểu sự phụ thuộc giữa các thành phần, giúp bạn áp dụng hiệu quả trong các dự án thực tế.
2. Khái niệm về Coupling
Coupling (hoặc sự liên kết/ghép nối) là mức độ phụ thuộc giữa các module, lớp (class) hoặc thành phần trong một hệ thống phần mềm. Nó thể hiện cách mà một thành phần "biết" về thành phần khác và chúng phụ thuộc vào nhau như thế nào. Coupling có thể được phân loại thành hai hình thức:
- Tight Coupling
- Loose Coupling
3. Tight Coupling
Tight Coupling xảy ra khi các lớp hoặc module phụ thuộc chặt chẽ vào nhau. Sự thay đổi trong một lớp thường yêu cầu thực hiện thay đổi ở các lớp khác.
Ví dụ về Tight Coupling trong iOS
Trong ví dụ dưới đây, DatabaseManager
được gọi trực tiếp trong UserManager
. Nếu bạn muốn thay đổi cách lưu trữ dữ liệu (SQLite, CoreData, Realm), bạn sẽ phải sửa nhiều nơi:
swift
class DatabaseManager {
func saveUser(name: String) {
print("User \(name) saved to database")
}
}
class UserManager {
let dbManager = DatabaseManager() // Tight Coupling
func createUser(name: String) {
dbManager.saveUser(name: name)
}
}
Các vấn đề với Tight Coupling:
- Phụ thuộc vào lớp cụ thể: Lớp
UserManager
phụ thuộc trực tiếp vào lớpDatabaseManager
, tạo ra sự phụ thuộc chặt chẽ. - Khó khăn trong việc refactor: Nếu bạn muốn đổi sang sử dụng CoreData hoặc Realm, sẽ cần sửa code trong
UserManager
và tất cả các nơi khác sử dụngDatabaseManager
. - Khó khăn trong testing: Bạn không thể dễ dàng mock
DatabaseManager
để kiểm traUserManager
. Bạn buộc phải sử dụng lớp cụ thể trong unit test, dẫn đến việc kiểm thử phức tạp và không tin cậy. - Khả năng tái sử dụng thấp:
UserManager
chỉ hoạt động vớiDatabaseManager
, không thể tái sử dụng với các cơ sở dữ liệu khác. - Khó bảo trì và mở rộng: Thay đổi trong
DatabaseManager
có thể ảnh hưởng đếnUserManager
, khiến việc mở rộng và bảo trì trở nên khó khăn.
Ưu và nhược điểm của Tight Coupling:
Ưu điểm:
- Hiệu suất tốt hơn: Code chạy sẽ nhanh hơn một chút vì các thành phần kết nối trực tiếp với nhau.
- Đơn giản với ứng dụng nhỏ: Thao tác này dễ thực hiện hơn với những ứng dụng không quá phức tạp.
Nhược điểm:
- Khó duy trì: Thay đổi sẽ khó khăn và dễ gây ra lỗi.
- Khó tái sử dụng: Các thành phần không thể tách rời dễ dàng ra để sử dụng ở nơi khác.
- Khó kiểm thử: Không thể kiểm tra riêng từng thành phần.
4. Loose Coupling
Loose Coupling xảy ra khi các lớp độc lập với nhau và giao tiếp qua các giao diện hoặc trừu tượng. Phiên bản này giúp giảm sự phụ thuộc giữa các lớp bằng cách sử dụng protocol, dependency injection, hoặc các mẫu thiết kế như MVVM, VIPER, hay Coordinator. Thay đổi trong một lớp không làm ảnh hưởng đến các lớp khác, giúp code dễ bảo trì và mở rộng hơn.
Ví dụ về Loose Coupling trong iOS
Sử dụng Dependency Injection, khi UserManager
không phụ thuộc trực tiếp vào DatabaseManager
, mà sử dụng DatabaseServiceProtocol
, giúp bạn dễ dàng thay đổi cách lưu dữ liệu mà không phải sửa đổi mã nguồn của UserManager
:
swift
// Định nghĩa protocol cho các thao tác cơ sở dữ liệu
protocol DatabaseService {
func saveUser(name: String)
}
// Các cài đặt cụ thể
class SQLiteManager: DatabaseService {
func saveUser(name: String) {
print("User \(name) saved to SQLite database")
}
}
class CoreDataManager: DatabaseService {
func saveUser(name: String) {
print("User \(name) saved to CoreData")
}
}
class RealmManager: DatabaseService {
func saveUser(name: String) {
print("User \(name) saved to Realm")
}
}
// `UserManager` giờ phụ thuộc vào abstraction, không phải implementation cụ thể
class UserManager {
private let dbService: DatabaseService // Loose Coupling
// Dependency injection thông qua constructor
init(dbService: DatabaseService) {
self.dbService = dbService
}
func createUser(name: String) {
dbService.saveUser(name: name)
}
}
// Ví dụ sử dụng
let sqliteManager = SQLiteManager()
let userManager = UserManager(dbService: sqliteManager)
userManager.createUser(name: "John")
// Dễ dàng chuyển đổi sang cài đặt cơ sở dữ liệu khác
let coreDataManager = CoreDataManager()
let anotherUserManager = UserManager(dbService: coreDataManager)
anotherUserManager.createUser(name: "Jane")
Điểm khác biệt chính so với Tight Coupling:
- Abstraction qua Protocol: Tạo
DatabaseService
protocol để định nghĩa giao diện chung, tách bất kỳ interface nào khỏi implementation cụ thể. - Dependency Injection:
UserManager
không tự khởi tạoDatabaseManager
mà nhận dịch vụ từ bên ngoài thông qua constructor, cho phép dễ dàng thay đổi implementation. - Nhiều Implementation: Dễ dàng thêm các implementation mới mà không cần thay đổi mã của
UserManager
. - Testability: Có thể dễ dàng mock
DatabaseService
để kiểm traUserManager
, không phụ thuộc vào implementation cụ thể.
Lợi ích của Loose Coupling:
- Linh hoạt: Code dễ bảo trì và thay đổi, ít chịu ảnh hưởng khi có yêu cầu mới.
- Tăng khả năng tái sử dụng: Các thành phần có thể được sử dụng trong nhiều ngữ cảnh khác nhau.
- Cải thiện khả năng kiểm thử: Có thể test các thành phần riêng biệt một cách độc lập.
Nhược điểm của Loose Coupling:
- Độ phức tạp tăng cao: Nhiều lớp trừu tượng có thể làm cho hệ thống trở nên phức tạp hơn.
- Tiềm ẩn overhead hiệu suất: Các abstraction có thể làm giảm hiệu suất trong một số trường hợp.
5. Một số Pattern và Kiến trúc giúp đạt được Loose Coupling
Dưới đây là một số mẫu và kiến trúc giúp giảm coupling trong phát triển iOS:
- Dependency Injection (DI): Tiến hành inject sự phụ thuộc từ bên ngoài, sử dụng constructor injection hoặc property injection.
- Protocol-Oriented Programming (POP): Sử dụng protocol để tách biệt interface và implementation.
- MVVM Architecture: View chỉ giao tiếp với ViewModel thông qua binding, làm giảm sự phụ thuộc giữa các lớp.
- VIPER Architecture: Tách bạch các thành phần thành View, Interactor, Presenter, Entity, Router, giúp code trở nên modular và dễ test.
6. Khi nào nên sử dụng Tight Coupling và Loose Coupling?
Tiêu chí | Tight Coupling | Loose Coupling |
---|---|---|
Mức độ phụ thuộc | Cao | Thấp |
Bảo trì | Khó | Dễ |
Mở rộng | Khó | Dễ |
Dễ test | Khó | Dễ |
Tốc độ phát triển | Nhanh lúc đầu | Tốn thời gian thiết kế ban đầu |
Khi nào chọn Tight Coupling:
- Khi cần phát triển nhanh một prototype hoặc ứng dụng nhỏ mà không cần mở rộng trong tương lai.
Khi nào chọn Loose Coupling:
- Khi xây dựng ứng dụng lớn hoặc cần mở rộng và bảo trì dễ dàng.
- Khi muốn thực hiện các bài kiểm tra đơn vị dễ dàng hơn.
7. Kết luận
Tight Coupling có thể làm cho code của bạn khó bảo trì, mở rộng và kiểm thử. Ngược lại, Loose Coupling cho phép ứng dụng của bạn linh hoạt hơn bằng cách sử dụng protocol, dependency injection và các mẫu thiết kế như MVVM, VIPER. Trong phát triển ứng dụng iOS, việc áp dụng các tư tưởng này sẽ giúp bạn giảm coupling và viết code dễ dàng hơn.
Lời khuyên: Hãy cân bằng giữa Tight và Loose Coupling. Tránh lạm dụng abstraction quá mức, vì điều này có thể làm code của bạn phức tạp hơn không cần thiết.
Nếu bạn thấy có ý kiến gì cần đóng góp, hãy để lại bình luận bên dưới nhé! Mọi ý kiến của bạn đều là động lực để nâng cao chất lượng bài viết!
source: viblo