Singleton — چیست، نمونه تکی کلاس در iOS و Android

نویسنده: IT Sectr منتشر شده: 2026-02-17 زمان مطالعه: 8 دقیقه

Singleton (تکی) — الگوی ساختی که نمونه یگانه کلاس را تضمین می‌کند و نقطه دسترسی سراسری به آن فراهم می‌کند. Singleton به طور گسترده در توسعه موبایل برای منابع مشترک استفاده می‌شود: کلاینت‌های شبکه، پایگاه‌های داده، مدیران تنظیمات. این الگو در کتاب کلاسیک GoF (1994) توصیف شده و یکی از شناخته‌شده‌ترین‌ها باقی مانده است. بیشتر — در Refactoring Guru: Singleton.

نکات اصلی

  • Singleton — یک نمونه از کلاس را در کل برنامه تضمین می‌کند
  • نقطه دسترسی سراسری — ویژگی ایستا shared یا companion object
  • Thread safety — همگام‌سازی برای کار صحیح در محیط چندنخی لازم است
  • انتقاد — Singleton تست‌نویسی را دشوار می‌کند و وابستگی‌های پنهان ایجاد می‌کند
  • جایگزین‌ها — Dependency Injection، Service Locator برای جایگزینی Singleton

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 شی را یک بار ایجاد می‌کند و از طریق سازنده تزریق می‌کند.

Singleton در iOS روی Swift: shared و ویژگی‌های ایستا

Swift Singleton از طریق ویژگی ایستای shared با مقداردهنده اولیه خصوصی پیاده‌سازی می‌شود. از Swift 3، مقداردهی تنبل ویژگی‌های ایستا به طور تضمینی نخ‌ایمن است — کامپایلر به طور خودکار همگام‌سازی را از طریق dispatch_once اضافه می‌کند. کافی است static let shared = Class() اعلام کرده و init() را خصوصی کنید. Swift بعد از مقداردهی برای دسترسی تک‌نخی به همگام‌سازی اضافی نیاز ندارد.

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 جایگزین می‌شود که تست‌پذیری را بهبود می‌بخشد.

Singleton در Android روی Kotlin: companion object و object

Kotlin Singleton — ساده‌ترین راه: کلمه کلیدی object یک کلاس-تکی با مقداردهی تنبل در اولین دسترسی اعلام می‌کند. Kotlin object نخ‌ایمن است و به همگام‌سازی اضافی نیاز ندارد. اگر Singleton با پارامترهای سازنده نیاز باشد، از companion object با نماینده lazy استفاده می‌شود. در Android Singleton اغلب برای زمینه Application و خدماتی که از طریق Application.onCreate() مقداردهی می‌شوند ضروری است.

kotlin
// گزینه 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: dispatch_once، synchronized و lock

Thread safety — نیاز حیاتی برای Singleton در محیط چندنخی. بدون همگام‌سازی دو نخ می‌توانند همزمان instance == null را بررسی کرده و دو نمونه ایجاد کنند. راه‌حل — قفل در اولین ایجاد و آزادسازی بعد از مقداردهی. در Swift ویژگی‌های ایستا (static let) به طور پیش‌فرض نخ‌ایمن هستند. در Kotlin object نخ‌ایمن است. برای سبک Java در Kotlin از synchronized یا @Volatile + double-check locking استفاده می‌شود.

زبانمکانیزمنخ‌ایمنیمقداردهی تنبل
Swiftstatic letdispatch_once (خودکار)بله، در اولین دسترسی
Kotlin objectObject declarationمقداردهنده کلاس نخ‌ایمن استبله، در اولین دسترسی
Kotlin companionsynchronized + @VolatileDouble-checked lockingبله، از طریق lazy یا synchronized
Javasynchronized + volatileDouble-checked lockingبله، در getInstance()

Double-checked locking — الگویی برای مقداردهی تنبل Singleton. بررسی اول بدون همگام‌سازی (سریع، اگر نمونه قبلاً ایجاد شده)، دوم — داخل synchronized (ایجاد فقط توسط یک نخ). @Volatile دید تغییرات را برای همه نخ‌ها تضمین می‌کند. بدون volatile نخ دیگر ممکن است شی نیمه‌ساخته را ببیند. در Kotlin نماینده lazy با LazyThreadSafetyMode.SYNCHRONIZED به طور خودکار double-checked locking را پیاده‌سازی می‌کند.

Singleton vs Dependency Injection: چه موقع استفاده کنیم

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 — کلاس باز با ویژگی تزریق‌پذیر، که تست از طریق بازتاب یا setter mock را تنظیم می‌کند. چنین رویکردی سادگی Singleton را حفظ می‌کند اما ابزارهایی برای تست‌نویسی فراهم می‌کند. Google Hilt را برای Android توصیه می‌کند، Apple DI را برای iOS تحمیل نمی‌کند — انتخاب به تیم بستگی دارد.

سوالات متداول

Singleton — ضدالگو است؟

خیر، Singleton الگوی GoF است، اما استفاده نادرست مکرر آن را به ضدالگوی Global State تبدیل می‌کند. Singleton برای منابع فیزیکی یکتا (صفحه، چاپگر، سیستم فایل) توجیه دارد. مشکلات زمانی ایجاد می‌شوند که Singleton برای مدیریت داده‌ها استفاده می‌شود: وابستگی‌های پنهان، دشواری تست‌نویسی، نقض Single Responsibility Principle. جایگزین مدرن — DI با محدوده Singleton.

چگونه کد استفاده‌کننده از Singleton را تست کنیم؟

سه رویکرد: (1) از طریق پروتکل — Singleton پروتکل را پیاده‌سازی می‌کند، تست‌ها پیاده‌سازی را جایگزین می‌کنند؛ (2) از طریق DI — Singleton به عنوان وابستگی از طریق سازنده تزریق می‌شود؛ (3) از طریق متد بازنشانی — Singleton متدی برای بازنشانی حالت در تست‌ها دارد (فقط برای بیلد تست). رویکرد اول ترجیح داده می‌شود، سوم — برای تولید خطرناک است. Swift اجازه می‌دهد ویژگی shared را از طریق دستکاری runtime در تست‌ها جایگزین کنید.

Kotlin object چه تفاوتی با Java Singleton دارد؟

Kotlin object — ساختار زبانی است که Singleton را در سطح بایت‌کد ایجاد می‌کند. برخلاف پیاده‌سازی Java با سازنده خصوصی و getInstance()، object نخ‌ایمنی، مقداردهی تنبل و ممنوعیت وراثت را تضمین می‌کند. Java Singleton برای کار صحیح در محیط چندنخی به همگام‌سازی دستی (synchronized) و volatile نیاز دارد. Kotlin object — امن‌ترین و مختصرترین راه در Android است.

آیا می‌توان Singleton را به ارث برد؟

وراثت Singleton الگو را نقض می‌کند: اگر کلاس Singleton قابل ارث‌بردن باشد، زیرکلاس می‌تواند نمونه دوم ایجاد کند و یکتایی را نقض کند. در Swift final class وراثت را ممنوع می‌کند. Kotlin object قابل ارث‌بردن نیست (object — sealed). اگر Singleton با تنوع نیاز است، از کانتینر DI با محدوده Singleton استفاده کنید: یک نمونه را تضمین می‌کند و وراثت را از طریق رابط‌ها پشتیبانی می‌کند.

چگونه پارامترها را به Singleton در Android منتقل کنیم؟

پارامترها از طریق init(context: Application) یا getInstance(param) منتقل می‌شوند. Kotlin object پارامتر نمی‌پذیرد — از companion object با متد کارخانه‌ای getInstance(param) استفاده کنید. Hilt مشکل را حل می‌کند: @Singleton + @Inject constructor(context: Application) — کانتینر DI زمینه Application را به طور خودکار تزریق می‌کند. برای کلاینت retrofit پارامترها (baseUrl، interceptors) از طریق builder در ماژول DI منتقل می‌شوند.

خلاصه

  • Singleton — الگو با نمونه یگانه و دسترسی سراسری
  • Swift shared — static let با نخ‌ایمنی از کامپایلر
  • Kotlin object — مقداردهی تنبل بدون کد اضافی
  • Thread safety — double-checked locking برای Java، خودکار برای Swift/Kotlin
  • Apple SDK — UIApplication.shared، UserDefaults.standard، FileManager.default
  • Android SDK — Retrofit، Room، SharedPreferences از طریق مدیران Singleton
  • جایگزین‌ها — Dependency Injection برای کد تست‌پذیر

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید