Singleton — градивни образац који гарантује јединствену инстанцу класе и пружа глобалну тачку приступа њој. Singleton се широко користи у мобилном развоју за дељене ресурсе: мрежни клијенти, базе података, менаџери подешавања. Образац је описан у класичној књизи GoF (1994) и остаје један од најпрепознатљивијих. Детаљније — на Refactoring Guru: Singleton.
Главно
Singleton — градивни образац који је описао GoF (Gang of Four) 1994. године. Образац решава два задатка: ограничава стварање инстанце класе на један објекат и пружа глобални приступ том објекту. Singleton је користан за ресурсе који морају бити јединствени: фабрика сесија, кеш слика, менаџер повезивања са базом података, Crashlytics или Analytics клијент.
Имплементација Singleton захтева приватни конструктор (забрањује спољно стварање), статичко поље са јединственом инстанцом и статички метод приступа (shared, instance, getInstance). Клијенти позивају Singleton.shared.method() без бриге о стварању објекта. Образац је популаран у iOS и Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — све су то Singleton. Међутим, прекомерна употреба Singleton води до антиобрасца Global State.
Проблеми Singleton — скривене зависности (класе имплицитно зависе од Singleton објекта), тешкоћа тестирања (не може се заменити инстанца у тесту без додатног напора), кршење Single Responsibility Principle (Singleton управља и својом инстанцом и пословном логиком). Савремени мобилни развој преферира DI (Dagger, Hilt, Swinject) за управљање јединственим инстанцама — DI контејнер ствара објекат једном и убризгава га кроз конструктор.
Swift Singleton се имплементира кроз статичко својство shared са приватним иницијализатором. Од Swift 3, лења иницијализација статичких својстава је гарантовано нитно безбедна — компајлер аутоматски додаје синхронизацију кроз dispatch_once. Довољно је декларисати static let shared = Class() и учинити init() приватим. Swift не захтева додатну синхронизацију за једнонитни приступ након иницијализације.
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
}
}
// Коришћење
let data = try await NetworkManager.shared.fetchData(from: url)
Apple Singleton — у iOS SDK многи објекти користе Singleton: UIApplication.shared, UIScreen.main, FileManager.default, NotificationCenter.default, UserDefaults.standard. Apple користи Singleton за услуге које су физички јединствене (један екран, једна апликација). Програмери копирају овај образац за своје услуге. У SwiftUI глобални приступ Singleton-у се замењује Environment и @EnvironmentObject, што побољшава тестирабилност.
Kotlin Singleton — најједноставнији начин: кључна реч object декларише класу-синглтон са лењом иницијализацијом при првом приступу. Kotlin object је нитно безбедан и не захтева додатну синхронизацију. Ако је потребан Singleton са параметрима конструктора, користи се companion object са lazy делегатом. У Android-у Singleton је често неопходан за Application контекст и услуге које се иницијализују кроз Application.onCreate().
// Варијанта 1: object — једноставан Singleton без параметара
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: companion object — Singleton са параметрима
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 — многи системски сервиси Android-а имплементирају Singleton: context.getSystemService(), Room.databaseBuilder(), Retrofit.Builder(). Примери укључују SharedPreferences, MediaPlayer, AudioManager. У Android апликацијама Singleton се често користи за репозиторијуме, менаџере и фабрике. Google препоручује замену Singleton-а са DI (Hilt, Koin), где Singleton опсег (Scope.Singleton или @Singleton) управља контејнер, а класа остаје тестирабилна.
Thread safety — критичан захтев за Singleton у вишенитном окружењу. Без синхронизације две нити могу истовремено проверити instance == null и створити две инстанце. Решење — блокирање при првом стварању и отпуштање након иницијализације. У Swift-у статичка својства (static let) су подразумевано нитно безбедна. У Kotlin-у object је нитно безбедан. За Java стил у Kotlin-у се користи synchronized или @Volatile + double-check locking.
| Језик | Механизам | Нитна безбедност | Лења иницијализација |
|---|---|---|---|
| Swift | static let | dispatch_once (ауто) | Да, при првом приступу |
| Kotlin object | Object declaration | Иницијализатор класе је нитно безбедан | Да, при првом приступу |
| Kotlin companion | synchronized + @Volatile | Double-checked locking | Да, кроз lazy или synchronized |
| Java | synchronized + volatile | Double-checked locking | Да, у getInstance() |
Double-checked locking — образац за лењу иницијализацију Singleton. Прва провера без синхронизације (брза, ако инстанца већ постоји), друга — унутар synchronized (стварање само једном нити). @Volatile гарантује видљивост промена за све нити. Без volatile друга нити може видети делимично створен објекат. У Kotlin-у lazy делегат са LazyThreadSafetyMode.SYNCHRONIZED аутоматски имплементира double-checked locking.
Dependency Injection — алтернатива Singleton-у за управљање јединственом инстанцом. DI контејнер (Dagger, Hilt, Koin, Swinject) ствара објекат једном у Singleton опсегу и убризгава га кроз конструктор. Класа не зна за свој Singleton статус — то одлучује контејнер. Код постаје тестирабилан: у тесту DI модул се замењује mock модулом. Предности DI: експлицитне зависности у конструктору, могућност преоптерећења, јединствени животни циклус.
Када је Singleton оправдан — објекти системског нивоа: Crashlytics, Analytics, Logging. Ове услуге се иницијализују једном у AppDelegate/Application и користе се свуда. DI је за њих претерано. Singleton је такође погодан за кешове слика (NSCache, Coil, Glide), где је глобални приступ оправдан перформансама. За све остало DI је пожељнији: чини зависности видљивим, поједностављује тестирање и рефакторисање.
Хибридни приступ — Singleton са могућношћу преоптерећења за тестове. У Swift-у се користи протокол + статичко својство које тест може заменити (нпр. кроз URLProtocol за URLSession). У Kotlin-у — open класа са injectable својством, где тест поставља mock кроз рефлексију или setter. Овај приступ задржава једноставност Singleton-а, али пружа алате за тестирање. Google препоручује Hilt за Android, Apple не намеће DI за iOS — избор зависи од тима.
Често постављана питања
Не, Singleton је GoF образац, али његова честа неправилна употреба претвара га у антиобразац Global State. Singleton је оправдан за физички јединствене ресурсе (екран, штампач, фајл систем). Проблеми настају када се Singleton користи за управљање подацима: скривене зависности, тешкоћа тестирања, кршење Single Responsibility Principle. Савремена алтернатива — DI са Singleton опсегом.
Три приступа: (1) кроз протокол — Singleton имплементира протокол, тестови замењују имплементацију; (2) кроз DI — Singleton се убризгава као зависност кроз конструктор; (3) кроз reset метод — Singleton има метод за ресетовање стања у тестовима (само за тест билд). Први приступ је пожељнији, трећи — опасан за production. Swift омогућава замену shared својства кроз runtime манипулације у тестовима.
Kotlin object — језичка конструкција која ствара Singleton на нивоу бајткода. За разлику од Java имплементације са приватим конструктором и getInstance(), object гарантује нитну безбедност, лењу иницијализацију и забрану наслеђивања. Java Singleton захтева ручну синхронизацију (synchronized) и volatile за исправан рад у вишенитном окружењу. Kotlin object — најбезбеднији и најконцизнији начин у Android-у.
Наслеђивање Singleton крши образац: ако се Singleton класа може наследити, подкласа може створити другу инстанцу, нарушавајући јединственост. У Swift-у final class забрањује наслеђивање. Kotlin object не може бити наследјен (object — sealed). Ако је потребан Singleton са варијабилношћу, користите DI контејнер са Singleton опсегом: гарантује једну инстанцу и подржава наслеђивање кроз интерфејсе.
Параметри се прослеђују кроз init(context: Application) или getInstance(param). Kotlin object не прихвата параметре — користите companion object са фабричким методом getInstance(param). Hilt решава проблем: @Singleton + @Inject constructor(context: Application) — DI контејнер убризгава Application контекст аутоматски. За retrofit клијент параметри (baseUrl, interceptors) се прослеђују кроз builder у DI модулу.
Завршни
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође