Coupling (keterikatan) adalah metrik yang menunjukkan seberapa besar ketergantungan satu modul aplikasi terhadap modul lainnya. Menurut Wikipedia, keterikatan lemah (low coupling) adalah ciri sistem yang dirancang dengan baik, di mana modul dapat diubah tanpa merusak modul di sekitarnya. Pengelolaan coupling adalah salah satu tugas utama arsitek saat merancang aplikasi mobile.
Poin Utama
Coupling (keterikatan) adalah metrik yang menentukan seberapa erat satu modul atau kelas terikat dengan yang lain. Semakin banyak satu modul tahu tentang struktur internal modul lain, semakin tinggi coupling dan semakin sulit mengubah sistem. Dalam arsitektur yang dirancang dengan baik, coupling harus minimal — modul hanya berinteraksi melalui antarmuka yang ditentukan secara ketat.
Dua sisi coupling dibedakan: afferent (ketergantungan masuk — berapa banyak modul bergantung pada suatu modul) dan efferent (ketergantungan keluar — suatu modul bergantung pada berapa banyak modul). Analisis metrik ini memungkinkan identifikasi "titik panas" dalam arsitektur, di mana perubahan satu modul akan memengaruhi banyak modul lainnya. Alat seperti IntelliJ Dependency Analyzer dan Xcode Graph memvisualisasikan hubungan ini.
Penting untuk dipahami bahwa coupling nol tidak mungkin — modul harus saling berinteraksi, jika tidak, itu bukan sistem, melainkan kumpulan program yang terisolasi. Tugas arsitek adalah membuat coupling dapat dikelola dan transparan. Ideal: modul hanya berinteraksi melalui antarmuka dan hanya mengirimkan data sederhana, tanpa mengetahui struktur internal satu sama lain. Ini disebut loose coupling (keterikatan lemah).
Enam tipe coupling membentuk skala dari terbaik hingga terburuk. Memahami skala ini membantu mengevaluasi kode yang ada dan memilih arah refactoring. Sebagian besar proyek mobile memiliki tipe coupling campuran, dan tugas arsitek adalah secara bertahap mengganti tipe kuat dengan yang lemah.
Data coupling (keterikatan data) — modul hanya bertukar data sederhana melalui parameter metode. Modul A memanggil metode modul B, mengirimkan primitif atau struktur sederhana, dan menerima hasil. Modul A tidak tahu bagaimana B diimplementasikan di dalam. Ini adalah tipe coupling yang paling diinginkan: meminimalkan konsekuensi perubahan.
Contoh: EmailValidator.isValid(email: String): Boolean. Kelas konsumen mengirimkan string dan menerima Boolean, tanpa mengetahui ekspresi reguler atau aturan validasi di dalam validator. Perubahan logika validasi tidak memerlukan perubahan konsumen — coupling minimal. Data coupling adalah tujuan untuk semua antarmuka publik dalam aplikasi.
Stamp coupling (keterikatan struktur) — modul bertukar objek gabungan, tetapi hanya menggunakan sebagian dari field mereka. Modul A mengirimkan objek User ke metode calculateDiscount, yang hanya menggunakan user.status. Masalah: jika struktur User berubah (field wajib ditambahkan), modul calculateDiscount tidak berubah, tetapi konsumen yang membuat objek User akan berubah.
Dalam praktiknya, stamp coupling tidak terhindarkan dan dapat diterima jika objek yang dikirim adalah model data standar (Entity). Masalah muncul ketika modul menerima seluruh objek hanya untuk satu field. Dalam kasus seperti itu, lebih baik mengirimkan nilai spesifik secara langsung (data coupling). Solusi — analisis penggunaan field oleh pihak penerima.
Control coupling — satu modul mengirimkan flag ke modul lain yang mengontrol perilakunya (calculate(useNewAlgorithm: Boolean)). Ini lebih buruk dari stamp coupling karena modul konsumen harus mengetahui varian internal kerja modul yang dipanggil. Solusi: bagi metode menjadi dua — calculateWithNewAlgorithm() dan calculateWithLegacyAlgorithm().
External coupling — modul bergantung pada protokol eksternal, format data, atau API. Semua modul yang mem-parsing JSON yang sama atau bekerja dengan database yang sama memiliki external coupling. Tidak dapat dihindari sepenuhnya, tetapi dapat diisolasi: buat lapisan pemetaan antara format eksternal dan model internal. Common coupling — modul berbagi status global bersama. Content coupling — tipe terburuk, ketika modul secara langsung mengubah data internal modul lain.
| Tipe Coupling | Tingkat | Deskripsi |
|---|---|---|
| Data | Terbaik | Mengirimkan data sederhana melalui parameter |
| Stamp | Dapat diterima | Mengirimkan objek dengan penggunaan sebagian |
| Control | Sedang | Mengontrol perilaku melalui flag |
| External | Tinggi | Ketergantungan pada protokol/format eksternal |
| Common | Sangat tinggi | Berbagi status global |
| Content | Tidak dapat diterima | Perubahan langsung data internal modul |
Skala coupling dari data (ideal) hingga content (bencana) — alat praktis untuk tinjauan kode. Jika Anda melihat common atau content coupling dalam proyek — itu adalah target prioritas refactoring. Data dan stamp coupling dapat diterima dan ada di setiap proyek, tetapi jumlahnya harus dikontrol.
Coupling tinggi mengubah pengembangan menjadi proses yang lambat, di mana setiap perubahan memerlukan pemeriksaan puluhan modul yang berpotensi rusak. Dalam pengembangan mobile, ini sangat kritis: platform diperbarui setiap tahun (Android API Level, iOS SDK), perpustakaan — setiap kuartal, dan kebutuhan bisnis — terus-menerus. Keterikatan lemah adalah satu-satunya cara untuk mengatasi aliran perubahan ini tanpa regresi yang konstan.
Contoh dari praktik: aplikasi mobile di mana semua layar langsung mengimpor NetworkingManager dan DatabaseManager. Saat mengganti klien HTTP dari Retrofit ke Ktor (Android) atau dari URLSession ke Alamofire (iOS), pengembang harus memperbaiki setiap layar. Dengan coupling rendah, cukup mengubah satu implementasi yang tersembunyi di balik antarmuka NetworkDataSource — konsumen tidak akan melihat pergantian.
Pengaruh coupling pada pengujian unit juga sangat besar. Kelas dengan coupling tinggi (pembuatan ketergantungan langsung melalui konstruktor) tidak dapat diuji secara terisolasi — ia membawa serta database, jaringan, dan UI. Untuk menguji kelas seperti itu, Anda harus menjalankan emulator dan menunggu pengujian integrasi. Kelas dengan coupling rendah menerima ketergantungan melalui constructor injection dan mudah di-mock.
// Coupling tinggi — kelas membuat ketergantungannya sendiri
class ProfileViewModelHigh {
private val api = RetrofitApi()
private val db = RoomDatabase.getInstance()
private val cache = MemoryCache()
}
// Coupling rendah — ketergantungan dikirimkan melalui konstruktor
class ProfileViewModelLow(
private val api: ApiService,
private val db: DatabaseService,
private val cache: CacheService
)
Dalam kasus pertama, ProfileViewModelHigh terikat erat pada implementasi konkret — mengganti Retrofit dengan Ktor memerlukan perubahan kode ViewModel. Dalam kasus kedua, ProfileViewModelLow hanya bergantung pada antarmuka, yang implementasinya disediakan dari luar. Menguji kelas kedua sangat mudah: kami mengirimkan implementasi mock dan memeriksa logika tanpa emulator.
Dependency Inversion Principle (D dalam SOLID) — dasar untuk mengurangi coupling. Prinsip ini menetapkan untuk bergantung pada abstraksi, bukan pada implementasi konkret. Alih-alih kelas secara langsung membuat objek RetrofitApi, ia harus menerima antarmuka ApiService. Ini memindahkan ikatan dari perpustakaan tertentu ke tingkat abstraksi, yang dapat diganti tanpa mengubah konsumen.
Observer pattern (atau versi reaktifnya — StateFlow, Combine Publishers) mengurangi coupling antara sumber data dan pelanggan. Pelanggan tidak tahu dari mana data berasal — ia hanya bereaksi terhadap perubahan. Ini memisahkan pengirim dan penerima: sumber data baru dapat ditambahkan tanpa mengubah pelanggan yang ada. EventBus dan SharedFlow bekerja dengan prinsip yang sama.
Bridge pattern memisahkan abstraksi dan implementasi, memungkinkan mereka berubah secara independen. Dalam pengembangan mobile, Bridge diterapkan, misalnya, untuk modul yang bergantung pada platform: antarmuka ImageLoader bersama dengan implementasi berbeda untuk iOS (Kingfisher, Nuke) dan Android (Glide, Coil). Kode yang bekerja dengan ImageLoader tidak bergantung pada perpustakaan yang dipilih dan dapat menggantinya dengan perubahan implementasi sederhana.
Dependency Injection (DI) — alat paling praktis untuk mengurangi coupling dalam pengembangan mobile. Alih-alih kelas membuat ketergantungannya sendiri, wadah DI (Hilt, Koin, Dagger untuk Android; Swinject, Factory untuk iOS) menyediakannya dari luar. Kelas menerima ketergantungan melalui constructor, method, atau property injection, tetap tidak mengetahui implementasi konkret.
DI mendokumentasikan secara eksplisit ketergantungan kelas: cukup melihat konstruktor untuk memahami dengan modul apa kelas berinteraksi. Jika konstruktor menerima 8 parameter dari berbagai lapisan — ini adalah sinyal coupling berlebihan yang memerlukan refactoring. Praktik yang baik — tidak lebih dari 3-4 ketergantungan per kelas. Jumlah yang lebih besar menunjukkan pelanggaran Single Responsibility dan coupling berlebihan.
DI juga menyederhanakan pengujian: untuk setiap pengujian, Anda membuat kelas dengan ketergantungan mock, tanpa memerlukan database atau jaringan nyata. Di Flutter, DI diimplementasikan melalui Provider, Riverpod, atau GetIt. Terlepas dari frameworknya, tujuannya sama: memperlemah ikatan antar modul dengan membuat ketergantungan menjadi eksplisit dan dapat diganti. Penerapan DI dalam proyek mobile adalah standar de facto sejak tahun 2020-an.
// Wadah DI membangun grafik ketergantungan
protocol AuthServiceProtocol {
func login(email: String, password: String) async throws -> User
}
final class AuthService: AuthServiceProtocol {
func login(email: String, password: String) async throws -> User {
// implementasi
}
}
// ViewModel tidak tahu tentang layanan konkret — hanya protokol
final class LoginViewModel {
private let auth: AuthServiceProtocol
init(auth: AuthServiceProtocol) {
self.auth = auth
}
}
// DI Container — satu-satunya tempat di mana tipe konkret dibuat
final class DIContainer {
lazy var authService: AuthServiceProtocol = AuthService()
lazy var loginViewModel: LoginViewModel {
LoginViewModel(auth: self.authService)
}
}
Di sini LoginViewModel hanya bergantung pada protokol AuthServiceProtocol, bukan pada AuthService konkret. Mengganti implementasi (misalnya, beralih dari Firebase Auth ke server sendiri) hanya memerlukan perubahan di DIContainer. Semua konsumen AuthServiceProtocol tetap tidak tersentuh — coupling diminimalkan melalui abstraksi dan DI.
Pertanyaan yang Sering Diajukan
Cohesion mengukur koherensi internal modul, coupling — keterikatan eksternal antar modul. Arsitektur yang baik menginginkan cohesion tinggi dan coupling rendah. Metrik ini berbanding terbalik: peningkatan cohesion biasanya mengurangi coupling dan sebaliknya.
Data dan stamp — normal dan ada di setiap proyek. Control coupling dapat diterima dalam skenario terbatas (misalnya, strategy pattern). External coupling tidak terhindarkan saat bekerja dengan API eksternal, tetapi harus diisolasi di balik lapisan pemetaan. Common dan content coupling — tanda masalah arsitektur yang memerlukan refactoring segera.
Alat analisis statis: IntelliJ IDEA Dependency Matrix, Xcode Graph, Gradle Dependencies report, SonarQube. Metrik: afferent coupling (Ca), efferent coupling (Ce), Instability (Ce/(Ca+Ce)). Instability tinggi (mendekati 1) berarti modul mudah diubah dan sedikit yang mereferensikannya — ini bagus.
Coupling yang sangat rendah dapat berarti jumlah abstraksi dan antarmuka yang berlebihan yang mempersulit navigasi kode. Jika untuk setiap kelas dibuat antarmuka terpisah, programmer membuang waktu untuk melompat antar file. Keseimbangan: antarmuka untuk API eksternal modul, tetapi tidak untuk setiap kelas pembantu internal.
Gunakan teknik Strangler Fig — secara bertahap ganti panggilan langsung melalui antarmuka. Mulailah dengan extract interface untuk kelas yang paling sering dirujuk. Kemudian perkenalkan wadah DI. Lindungi kode yang diisolasi dengan pengujian karakterisasi untuk memastikan refactoring tidak mengubah perilaku sistem.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga