Service Locator — inti pola, registri pusat dan DI

Penulis: IT Sectr Diterbitkan: 2026-02-18 Waktu membaca: 9 mnt

Service Locator — pola arsitektur yang menyediakan registri pusat (registry) layanan. Kode klien meminta layanan melalui locator statis, tanpa membuatnya secara langsung atau menerimanya melalui konstruktor. Service Locator sering dianggap sebagai alternatif Dependency Injection: lebih sederhana dalam implementasi, tetapi menyembunyikan ketergantungan dan mempersulit pengujian. Pola ini diimplementasikan melalui kelas Singleton global dengan registrasi dan resolusi layanan. Lebih lanjut — dalam perbandingan DI dan Service Locator oleh Martin Fowler.

Poin Utama

  • Service Locator — registri pusat (registry) untuk mendapatkan layanan
  • Alternatif DI — lebih sederhana dalam implementasi, tetapi menyembunyikan ketergantungan dari klien
  • Antipola — banyak pengembang menganggap Service Locator sebagai antipola karena ketergantungan yang tersembunyi
  • Akses global — locator tersedia secara statis dari mana saja, tanpa pengiriman melalui konstruktor
  • Pengujian — lebih sulit daripada DI: memerlukan konfigurasi locator global untuk setiap pengujian

Apa itu Service Locator: inti dan struktur pola

Service Locator — pola yang memusatkan pembuatan dan penyediaan layanan. Di dasarnya adalah kelas Singleton (Locator) yang berisi registri (Registry) layanan: kamus di mana kuncinya adalah jenis (atau pengidentifikasi) layanan dan nilainya adalah implementasi konkret. Kode klien memanggil ServiceLocator.resolve(ServiceProtocol.self) dan menerima instance yang sudah jadi. Pola ini tidak menentukan bagaimana layanan dibuat — pabrik, container DI, atau new di dalam locator.

Struktur pola — Registry (registri tipe [String: Any]), Locator (kelas statis dengan register dan resolve), Service (layanan yang dapat didaftarkan). Registri dapat menyimpan pabrik (closure/lambda untuk membuat objek) atau instance yang sudah jadi. Locator dapat bersifat global (satu per aplikasi) atau terbatas lingkup (per fitur/modul). Resolusi layanan adalah pencarian dalam kamus berdasarkan jenis. Swift dan Kotlin menggunakan jenis sebagai kunci melalui metatype: ObjectIdentifier(ServiceProtocol.self).

KomponenTanggung jawabSwift/Kotlin
ServiceLocatorAkses global ke layananclass ServiceLocator
RegistryPenyimpanan pabrik/instance[ObjectIdentifier: Any]
ServiceImplementasi konkretNetworkService()

Sejarah pola — Service Locator dijelaskan dalam buku Java Patterns (1998) dan kemudian dalam Core J2EE Patterns (2001). Martin Fowler dalam artikel tahun 2004 membandingkan Service Locator dengan DI, mencatat bahwa Service Locator adalah „alternatif yang lebih sederhana, tetapi lebih buruk untuk pengujian“. Dalam pengembangan seluler, Service Locator digunakan dalam proyek Android awal dan aplikasi iOS sebelum munculnya Dagger dan Swinject. Saat ini pola ini lebih sering ditemukan dalam proyek lama dan prototipe.

Service Locator di Swift: registri layanan global

Service Locator di Swift — implementasi melalui properti statis dan kamus thread-safe. Digunakan registri dengan ObjectIdentifier(Protocol.self) sebagai kunci dan pabrik (()->Any) sebagai nilai. Inisialisasi malas (lazy var) — praktik standar: layanan dibuat saat permintaan pertama. Swift memerlukan konversi tipe eksplisit saat resolve: guard let service = locator.resolve(ServiceProtocol.self) else { return }.

swift
final class ServiceLocator {
    static let shared = ServiceLocator()
    private var registry: [ObjectIdentifier: Any] = [:]
    private let lock = NSLock()

    func register<T>(_ type: T.Type, factory: @escaping () -> T) {
        lock.lock()
        registry[ObjectIdentifier(type)] = factory
        lock.unlock()
    }

    func resolve<T>(_ type: T.Type) -> T {
        lock.lock()
        defer { lock.unlock() }
        guard let factory = registry[ObjectIdentifier(type)] as? () -> T else {
            fatalError("Service \(type) not registered")
        }
        return factory()
    }

    func reset() {
        lock.lock()
        registry.removeAll()
        lock.unlock()
    }
}

// Pendaftaran
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }

// Penggunaan
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)

Manajemen lingkup — locator dapat menyimpan pabrik (transient — objek baru setiap kali) atau instance yang sudah jadi (singleton). Untuk pabrik, closure didaftarkan yang dipanggil pada setiap resolve. Untuk singleton — closure dengan menangkap instance yang dibuat. Menambahkan lingkup: .transient, .singleton, .weak (referensi lemah — objek hidup selama seseorang memegang referensi). Lingkup weak berguna untuk UIKit ViewController untuk menghindari kebocoran memori saat pop/dismiss.

Service Locator di Kotlin: registrasi malas

Service Locator di Kotlin — implementasi ringkas melalui object (Singleton) dengan fungsi inline reified untuk keamanan tipe. Kotlin memungkinkan pembuatan locator yang ringkas: val service by locator<ServiceProtocol>() dengan delegasi, yang membuat kode lebih bersih. Reified generics (<reified T>) menggantikan ObjectIdentifier — tipe diperoleh dari generic. Locator Kotlin sering menggunakan ConcurrentHashMap untuk keamanan thread tanpa penguncian eksplisit.

kotlin
object ServiceLocator {
    private val registry = ConcurrentHashMap<Class<*>, () -> Any>()

    inline fun <reified T: Any> register(noinline factory: () -> T) {
        registry[T::class.java] = factory
    }

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T: Any> resolve(): T {
        val factory = registry[T::class.java]
            ?: throw IllegalStateException("Service ${T::class.simpleName} not registered")
        return factory() as T
    }

    fun clear() {
        registry.clear()
    }
}

// Pendaftaran
ServiceLocator.register<ApiService> { RetrofitApiService() }

// Penggunaan di kelas
class UserRepository {
    private val api: ApiService = ServiceLocator.resolve()
    private val db: Database = ServiceLocator.resolve()
}

// Delegasi untuk resolusi malas
class LocatorDelegate<reified T: Any> : Lazy<T> {
    override val value: T get() = ServiceLocator.<T>resolve()
    override fun isInitialized(): Boolean = true
}

inline fun <reified T: Any> locator(): Lazy<T> = LocatorDelegate()
// Penggunaan: val api by locator()

Service Locator di Android — ditemukan dalam proyek lama sebelum munculnya Dagger. Jetpack Hilt dan Koin telah menggantikan Service Locator di komunitas Android. Namun, locator tetap relevan untuk pengujian unit: tiruan sederhana ServiceLocator dengan layanan mock tanpa Hilt. Plus: tidak perlu menunggu kompilasi Dagger untuk pengujian. Minus: jika lupa mengganti locator dalam pengujian, pengujian menggunakan layanan produksi.

Service Locator vs DI: perbandingan dan kapan memilih apa

Kejelasan ketergantungan — perbedaan utama. DI mendeklarasikan ketergantungan secara eksplisit: init(service: ServiceProtocol) — IDE mana pun menunjukkan ketergantungan kelas. Service Locator menyembunyikannya: dependencies = ServiceLocator.resolve() — tersembunyi di dalam metode. Dengan DI Anda dapat langsung melihat semua ketergantungan kelas, dengan Service Locator Anda harus membaca seluruh tubuh kelas. Ini membuat kode pada Service Locator kurang dapat diprediksi: perubahan pada registri dapat merusak kelas mana pun yang menggunakan locator.

KarakteristikService LocatorDependency Injection
Visibilitas ketergantunganTersembunyi di dalam metodeEksplisit di konstruktor
PengujianKonfigurasi registri globalMock di konstruktor
ModularitasRegistri global — tidak modularModul dengan wadah berbeda
KompleksitasImplementasi sederhana, 50-100 barisMemerlukan Dagger/Swinject
Time-to-marketMulai cepatKonfigurasi wadah

Kapan Service Locator dibenarkan — prototipe dan MVP (mulai cepat tanpa konfigurasi). Proyek lama di mana kerangka DI tidak dapat ditambahkan (kompilasi kompleks, batasan linter). Pustaka instrumental (logging, pelaporan crash) — mereka sudah bersifat global. Untuk aplikasi produksi dengan tim dari 3 pengembang, DI direkomendasikan: ketergantungan eksplisit mengurangi jumlah kesalahan saat refactoring dan memudahkan pengenalan pengembang baru ke dalam proyek.

Masalah Service Locator dan alternatif

Ketergantungan tersembunyi — kelas yang menggunakan ServiceLocator.resolve() di dalam metode tidak dapat dianalisis secara statis. IDE tidak menampilkan ketergantungan, kompiler tidak memeriksa apakah layanan telah didaftarkan. Kesalahan „Service not registered“ hanya terjadi saat runtime. Refactoring menjadi berbahaya: menghapus layanan dari registri dapat merusak kelas mana pun dalam aplikasi. DI memecahkan masalah ini melalui pemeriksaan waktu kompilasi (Dagger) atau konstruktor eksplisit.

Masalah pengujian — setiap pengujian harus mengonfigurasi ServiceLocator.shared dengan semua ketergantungan. Setelah pengujian — mereset (reset()) status. Dalam eksekusi paralel pengujian, status global ServiceLocator.shared menyebabkan race condition: satu pengujian mendaftarkan mock, yang lain — menerima mock milik orang lain. Solusi: locator lingkup (satu per pengujian) atau ThreadLocal. DI memecahkan masalah ini dari akarnya: setiap pengujian membuat instance-nya sendiri dengan ketergantungan mock.

swift
// Masalah pengujian Service Locator
class LoginViewModelTests: XCTestCase {
    override func setUp() {
        super.setUp()
        // Konfigurasi registri global untuk pengujian
        ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
    }

    override func tearDown() {
        ServiceLocator.shared.reset()
        super.tearDown()
    }

    func testLogin() {
        let viewModel = LoginViewModel() // menggunakan ServiceLocator di dalam
        // pengujian...
    }
}

Alternatif Service Locator — DI (Dagger, Swinject), Factory Method, Ambient Context. Factory Method — pola sederhana tanpa status global: kelas pabrik membuat layanan dan dikirim melalui konstruktor. Ambient Context — alternatif thread-safe untuk cross-cutting concerns (logging, otorisasi). Alternatif terbaik — Constructor Injection dengan pabrik manual tanpa kerangka DI: pembuatan ketergantungan secara eksplisit di pabrik dengan pengiriman melalui konstruktor memberikan kejelasan DI tanpa kompleksitas konfigurasi Dagger/Swinject.

Pertanyaan yang Sering Diajukan

Apakah Service Locator adalah antipola?

Banyak pengembang menganggap Service Locator sebagai antipola karena menyembunyikan ketergantungan, mempersulit pengujian, dan menciptakan hubungan tersembunyi antar kelas. Namun, dalam prototipe, proyek kecil, dan untuk layanan global (logging, analitik) Service Locator dapat dibenarkan. Keputusan tergantung pada konteks: untuk aplikasi produksi dengan tim — DI, untuk satu pengembang pada prototipe — Service Locator.

Apa perbedaan Service Locator dengan container DI?

Container DI (Dagger, Swinject) menyuntikkan ketergantungan ke dalam objek secara otomatis — objek tidak tahu tentang keberadaan container. Service Locator — objek sendiri meminta ketergantungan dari registri. Container DI mematuhi prinsip IoC, Service Locator — melanggarnya: objek sendiri mengelola perolehan ketergantungannya. Container DI bekerja sebelum pembuatan objek (melalui konstruktor), Service Locator — di mana saja dalam kode.

Kapan menggunakan Service Locator di iOS?

Service Locator dibenarkan di iOS untuk: layanan global (Analytics, Logger, Crashlytics), prototipe di mana konfigurasi Swinject berlebihan, dan untuk pengujian unit modul lama yang besar. Untuk proyek iOS baru, disarankan Swinject atau DI manual melalui konstruktor. SwiftUI dengan Environment — juga bentuk DI yang menghindari Service Locator.

Bagaimana menghindari race condition di Service Locator?

Gunakan koleksi thread-safe (NSLock di Swift, ConcurrentHashMap di Kotlin). Untuk pengujian — ThreadLocal atau locator lingkup. Alternatif: async-local storage — layanan terikat pada coroutine/aktor. Solusi terbaik — hindari Service Locator untuk pengujian paralel dan gunakan DI dengan pembuatan objek eksplisit untuk setiap pengujian.

Apakah Service Locator adalah singleton?

Biasanya Service Locator diimplementasikan sebagai Singleton, tetapi ini tidak wajib. Anda dapat membuat instance locator untuk modul (feature-scoped locator) dan mengirimkannya melalui konstruktor. Feature-scoped locator memecahkan masalah status global, tetapi tidak memecahkan masalah ketergantungan tersembunyi. Pola seperti ini disebut Ambient Context atau Scoped Locator.

Kesimpulan

  • Service Locator — registri pusat untuk mendapatkan layanan melalui akses statis
  • Ketergantungan tersembunyi — kelemahan utama: ketergantungan tidak terlihat dalam tanda tangan kelas
  • Pengujian — lebih sulit daripada DI karena status global dan kebutuhan untuk mereset
  • Swift/Kotlin — implementasi melalui kamus thread-safe dan reified generics
  • Alternatif — DI (Dagger Hilt, Swinject) — standar untuk proyek produksi
  • Dibenarkan — dalam prototipe, untuk layanan global dan proyek lama

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.

Diskusikan proyek

Baca juga