Singleton — létrehozási minta, amely garantálja az osztály egyetlen példányát és globális hozzáférési pontot biztosít hozzá. Singleton széles körben használatos a mobilfejlesztésben megosztott erőforrásokhoz: hálózati kliensek, adatbázisok, beállításkezelők. A mintát a klasszikus GoF (1994) könyv írja le, és az egyik legismertebb maradt. Bővebben — a Refactoring Guru: Singleton oldalon.
Lényeg
Singleton — létrehozási minta, amelyet a GoF (Gang of Four) írt le 1994-ben. A minta két feladatot old meg: korlátozza az osztálypéldány létrehozását egy objektumra, és globális hozzáférést biztosít ehhez az objektumhoz. A Singleton hasznos olyan erőforrásokhoz, amelyeknek egyedinek kell lenniük: munkamenet-gyár, képgyorsítótár, adatbázis-kapcsolatkezelő, Crashlytics vagy Analytics kliens.
A Singleton megvalósítása privát konstruktort (külső létrehozás tiltása), statikus mezőt egyetlen példánnyal és statikus hozzáférési metódust (shared, instance, getInstance) igényel. A kliensek Singleton.shared.method() hívással érik el az objektumot anélkül, hogy annak létrehozásával kellene foglalkozniuk. A minta népszerű iOS-ben és Androidban: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — mind Singleton. A Singleton túlzott használata azonban a Global State anti-mintához vezet.
A Singleton problémái — rejtett függőségek (az osztályok implicit módon függnek a Singleton objektumtól), tesztelés nehézsége (a példány nem cserélhető ki a tesztben további erőfeszítés nélkül), a Single Responsibility Principle megsértése (a Singleton kezeli saját példányát és az üzleti logikát is). A modern mobilfejlesztés a DI-t (Dagger, Hilt, Swinject) részesíti előnyben az egyetlen példányok kezelésére — a DI konténer egyszer hozza létre az objektumot, és konstruktoron keresztül injektálja.
Swift Singleton egy shared statikus tulajdonságon keresztül valósul meg privát inicializálóval. Swift 3 óta a statikus tulajdonságok lusta inicializálása garantáltan szálbiztos — a fordító automatikusan hozzáadja a szinkronizációt dispatch_once segítségével. Elég deklarálni a static let shared = Class() kifejezést, és az init()-t priváttá tenni. A Swift nem igényel további szinkronizációt az egyszálú hozzáféréshez az inicializálás után.
final class NetworkManager {
// Thread-safe Singleton
static let shared = NetworkManager()
private init() {
URLSessionConfiguration.default.timeoutIntervalForRequest = 30
}
private var cache = NSCache<NSString, NSData>()
func fetchData(from url: URL) async throws -> Data {
let key = url.absoluteString as NSString
if let cached = cache.object(forKey: key) {
return cached as Data
}
let (data, _) = try await URLSession.shared.data(from: url)
cache.setObject(data as NSData, forKey: key)
return data
}
}
// Használat
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — az iOS SDK-ban sok objektum használ Singleton-t: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Az Apple a Singleton-t fizikailag egyedi szolgáltatásokhoz (egy képernyő, egy alkalmazás) használja. A fejlesztők ezt a mintát másolják saját szolgáltatásaikhoz. A SwiftUI-ban a Singleton-hoz való globális hozzáférést az Environment és @EnvironmentObject váltja fel, ami javítja a tesztelhetőséget.
Kotlin Singleton — a legegyszerűbb mód: az object kulcsszó singleton-osztályt deklarál lusta inicializálással az első hozzáféréskor. A Kotlin object szálbiztos és nem igényel további szinkronizációt. Ha paraméteres konstruktorú Singleton-ra van szükség, companion object használatos lazy delegálttal. Androidban a Singleton gyakran szükséges az Application kontextushoz és az Application.onCreate()-on keresztül inicializált szolgáltatásokhoz.
// 1. lehetőség: object — egyszerű Singleton paraméterek nélkül
object AppPreferences {
private val prefs = Application.instance
.getSharedPreferences("app", Context.MODE_PRIVATE)
var isFirstLaunch: Boolean
get() = prefs.getBoolean("first_launch", true)
set(value) = prefs.edit { putBoolean("first_launch", value) }
}
// 2. lehetőség: companion object — Singleton paraméterekkel
class ApiClient private constructor(baseUrl: String) {
companion object {
@Volatile
private var instance: ApiClient? = null
fun getInstance(baseUrl: String): ApiClient {
return instance ?: this.synchronized {
instance ?: ApiClient(baseUrl).also { instance = it }
}
}
}
fun request(endpoint: String): String { /* ... */ }
}
Android SDK Singleton — az Android számos rendszerszolgáltatása megvalósítja a Singleton-t: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Példák közé tartozik a SharedPreferences, MediaPlayer, AudioManager. Android alkalmazásokban a Singleton gyakran használatos adattárakhoz, kezelőkhöz és gyárakhoz. A Google a Singleton DI-vel (Hilt, Koin) való helyettesítését ajánlja, ahol a Singleton hatókör (Scope.Singleton vagy @Singleton) a konténer által kezelt, az osztály pedig tesztelhető marad.
Thread safety — kritikus követelmény a Singleton számára többszálú környezetben. Szinkronizáció nélkül két szál egyszerre ellenőrizheti az instance == null értéket, és két példányt hozhat létre. Megoldás — zárolás az első létrehozáskor és feloldás az inicializálás után. Swift-ben a statikus tulajdonságok (static let) alapértelmezés szerint szálbiztosak. Kotlin-ban az object szálbiztos. Java-stílushoz Kotlin-ban synchronized vagy @Volatile + double-check locking használatos.
| Nyelv | Mechanizmus | Szálbiztonság | Lusta inicializálás |
|---|---|---|---|
| Swift | static let | dispatch_once (automatikus) | Igen, első hozzáféréskor |
| Kotlin object | Object declaration | Osztályinicializáló szálbiztos | Igen, első hozzáféréskor |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Igen, lazy-n vagy synchronized-en keresztül |
| Java | synchronized + volatile | Double-checked locking | Igen, getInstance()-ben |
Double-checked locking — minta a Singleton lusta inicializálásához. Első ellenőrzés szinkronizáció nélkül (gyors, ha a példány már létezik), második — a synchronized blokkon belül (csak egy szál által létrehozva). A @Volatile garantálja a változások láthatóságát az összes szál számára. Volatile nélkül egy másik szál részben létrehozott objektumot láthat. Kotlin-ban a lazy delegált LazyThreadSafetyMode.SYNCHRONIZED módban automatikusan megvalósítja a double-checked locking-ot.
Dependency Injection — alternatíva a Singleton helyett az egyetlen példány kezelésére. A DI konténer (Dagger, Hilt, Koin, Swinject) egyszer hozza létre az objektumot a Singleton hatókörben, és konstruktoron keresztül injektálja. Az osztály nem tud a Singleton státuszáról — ezt a konténer dönti el. A kód tesztelhetővé válik: a tesztben a DI modul mock modulra cserélhető. A DI előnyei: explicit függőségek a konstruktorban, felülírhatóság, egységes életciklus.
Mikor indokolt a Singleton — rendszerszintű objektumok: Crashlytics, Analytics, Logging. Ezek a szolgáltatások egyszer inicializálódnak az AppDelegate/Application osztályban, és mindenhol használatosak. A DI túlzás számukra. A Singleton kényelmes képtárolókhoz is (NSCache, Coil, Glide), ahol a globális hozzáférés teljesítménnyel indokolt. Minden máshoz a DI előnyösebb: láthatóvá teszi a függőségeket, egyszerűsíti a tesztelést és a refaktorálást.
Hibrid megközelítés — Singleton felülírhatósági lehetőséggel a tesztekhez. Swift-ben protokoll + statikus tulajdonság használatos, amelyet a teszt felülírhat (pl. URLProtocol segítségével URLSession-hez). Kotlin-ban — nyitott osztály injectable tulajdonsággal, ahol a teszt reflexióval vagy setter-rel állítja be a mock-ot. Ez a megközelítés megőrzi a Singleton egyszerűségét, de eszközöket biztosít a teszteléshez. A Google a Hilt-et ajánlja Androidhoz, az Apple nem írja elő a DI-t iOS-hez — a választás a csapattól függ.
Gyakran Ismételt Kérdések
Nem, a Singleton GoF minta, de gyakori helytelen használata a Global State anti-mintává változtatja. A Singleton fizikailag egyedi erőforrásokhoz (képernyő, nyomtató, fájlrendszer) indokolt. Problémák akkor merülnek fel, amikor a Singleton-t adatkezelésre használják: rejtett függőségek, tesztelés nehézsége, a Single Responsibility Principle megsértése. Modern alternatíva — DI Singleton hatókörrel.
Három megközelítés: (1) protokollon keresztül — a Singleton megvalósít egy protokollt, a tesztek lecserélik a megvalósítást; (2) DI-n keresztül — a Singleton függőségként injektálódik a konstruktoron keresztül; (3) reset metóduson keresztül — a Singleton rendelkezik egy metódussal az állapot alaphelyzetbe állításához tesztekben (csak teszt buildhez). Az első megközelítés előnyösebb, a harmadik — veszélyes éles környezetben. A Swift lehetővé teszi a shared tulajdonság lecserélését runtime manipulációkkal a tesztekben.
A Kotlin object — nyelvi konstrukció, amely bytecode szinten hozza létre a Singleton-t. Ellentétben a Java megvalósítással, amely privát konstruktort és getInstance()-t használ, az object garantálja a szálbiztonságot, a lusta inicializálást és az öröklődés tilalmát. A Java Singleton manuális szinkronizációt (synchronized) és volatile-t igényel a többszálú környezetben való helyes működéshez. A Kotlin object — a legbiztonságosabb és legtömörebb mód Androidban.
A Singleton öröklése megsérti a mintát: ha a Singleton osztály örökölhető, az alosztály létrehozhat egy második példányt, megsértve az egyediséget. Swift-ben a final class tiltja az öröklődést. A Kotlin object nem örökölhető (az object sealed). Ha variációval rendelkező Singleton-ra van szükség, használjon DI konténert Singleton hatókörrel: garantál egy példányt, és támogatja az öröklődést interfészeken keresztül.
A paraméterek init(context: Application) vagy getInstance(param) segítségével adhatók át. A Kotlin object nem fogad paramétereket — használjon companion object-et getInstance(param) gyártó metódussal. A Hilt megoldja a problémát: @Singleton + @Inject constructor(context: Application) — a DI konténer automatikusan injektálja az Application kontextust. Retrofit kliens esetén a paraméterek (baseUrl, interceptors) a builder-en keresztül adhatók át a DI modulban.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is