الگوی طراحی Singleton در سوئیفت
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 5 دقیقه

الگوی طراحی Singleton در سوئیفت

الگوی طراحی یگانه (Singleton) انتقاد شده ترین الگوی طراحی در تمام دوران است. در این مقاله روش صحیح استفاده از این الگوی طراحی را در پروژه های iOS می آموزیم.

اکثر مردم این الگو را یک ضدالگو درنظر میگیرند. ولی Singleton چیست و چرا بد است؟

Singleton چیست؟

یک الگوی بسیار محبوب است و معمولا به دلیل سادگی مورد استفاده قرار میگیرد. یک کلاس singleton فقط یک نمونه در کل چرخه عمر برنامه می تواند داشته باشد و این نمونه فقط از طریق یک پراپرتی static قابل دسترس است و object آن باید به صورت مشترک و گلوبال نمونه سازی شده باشد. چیزی شبیه به متغیر گلوبال.

متغیر های گلوبال و state ها

الگوی طراحی singleton به دلیل داشتن state تغییرپذیر گلوبال شهرت بدی دارد. کلمه گلوبال حتی در بین توسعه دهنده های باتجربه نیز خوشایند نیست. متغیر و state های گلوبال مرکز عوارض جانبی (side effect) برنامه ها هستند. این متغیر ها از هرجای برنامه قابل دسترسی هستند بنابراین کلاس هایی که از آنها استفاده میکنند ناامن، وابسته و دارای state میشوند و اشکال زدایی آنها سخت تر خواهد شد. پس این روش خوبی برای اشتراک گذاشتن state بین object ها نیست.

عوارض جانبی (Side Effects):

باید متغیر ها تا جای ممکن ایزوله و محدود شوند (scope) تا حالت پذیری کد به کمترین مقدار برسد. این کار عوارض جانبی را از بین میبرد و کد را امن تر میکند. به مثال زیر توجه کنید:

var global = 0

func square(_ x: Int) -> Int {
    global = x
    return x * x
}

global = 1;
var result = square(5)
result += global
print(result)

تابع square توسط توسعه دهنده دیگری نوشته شده که بنا به دلایلی خواسته است ورودی را در همان متغیر گلوبال ذخیره کند. وقتی ما آن تابع را صدا میزنیم متوجه این موضوع نمیشویم تا وقتی که کد را برسی کنیم. تصور کنید که اگر این مشکل در پروژه ای که توسط چندین توسعه دهنده نوشته شده باشد چه ارتشی از باگ ها ایجاد خواهد کرد.

عمر مخفی Singleton

Singleton ها یکبار ایجاد میشوند و برای همیشه زندگی میکنن، آنها مانند متغیر های گلوبال عمل میکنند و به همین دلیل باید بسیار مراقب آنها باشید. فقط باید از state هایی همراه با singleton استفاده کرد که در چرخه عمر کامل برنامه دوام می آورند. به عنوان مثال تنظیمات کاربر مناسب این الگو نیست و باید در طراحی خود تجدید نظر کنید. همچنین سوئیفت به طور پیشفرض thread safe نیست و درصورت استفاده از singleton باید برای موضوع multi-threading نیز آماده باشید. اگر این اگو بسیار مشکل زا است ، بهتر نیست به کل از استفاده آنها اجتناب کنیم؟ پاسخ خیر است.

چه زمانی از Singleton استفاده کنیم؟

برای مثال UIApplication تقریبا یک singleton است چون فقط یک برنامه میتوانیم داشته باشیم و باید تا زمانی که آن را خاموش میکنیم، کار کند. مورد استفاده دیگر میتواند کلاس logger باشید. استفاده singleton در این مورد امن است چون در صورت خاموش یا روشن بودن تغییری در رفتار برنامه ما ایجاد نخواهد شد. هیچ کس صاحب یا مدیر این object نخواهد بود و فقط اطلاعات مورد نظر به آن داده میشود و هیچ مشکلی هم برای state ها به وجود نمی آید. نتیجه: کنسول یا کلاس logger یک حالت مطلوب برای استفاده از الگوی طراحی singleton است.

Console.default.notice("Hello I'm a singleton!")

singleton ها زیادی در فریمورک های اپل وجود دارد هرچند که همه آنها به طور کامل آبجکت singleton نیستند. مانند: URLSession.shared، Bundle.main، UIScreen.main UIApplication.shared، UserDefaults.standard و غیره.

کلاس هایی مانند مدیریت شبکه (Network Manager)، مکان یا مدیریت Core Data نباید به صورت singleton پیاده شوند چون بیش از یکی از آنها میتواند وجود داشته باشد.

الگوی طراحی singleton میتواند بسیار کاربردی باشد ولی باید با احتیاط از استفاده شود.

اگر میخواهید چیزی را به singleton تبدیل کنید، این سوالات را از خود بپرسید:

  • آیا چیزی مسئول، صاحب یا مدیر آن خواهد شد؟
  • آیا فقط یک نمونه خواهیم داشت؟
  • آیا state گلوبال خواهد بود؟
  • آیا حتما باید از object مشترک گلوبال استفاده کنم؟
  • آیا باید در طول چرخه کامل نرم افزار کار کند؟
  • آیا راه دیگری هم هست؟

اگر جواب به همه سوالات بالا مثبت است، پس میتوان به صورت امن از singleton یا متغیر های گلوبال برای ذخیره داده استفاده کرد.

چگونه در سوئیفت singleton ایجاد کنیم

ایجاد singleton در سوئیفت بسیار اسان است ولی قبل از انجام این الگوی طراحی بهتر است دوباره در مورد گزینه های دیگر فکر کنید:

class Singleton {

    static let shared = Singleton()

    private init() {
    }
}
let singleton = Singleton.shared

همچنین فراموش نکنید که init() را به صورت private تعریف کنید.

می توان فقط یک آبجکت singleton به اسم App ایجاد کرد و تمام state های گلوبال را در آن قرار داد. نامگذاری آن نیز کمک میکند تا بهتر عملکرد آن را درک کنیم.

چگونه singleton را از بین ببریم؟

در 90 درصد مواقع راه دیگری نیز وجود دارد که بهتر است از آن استفاده کنید. رایج ترین راه تزریق وابستگی (dependency injection) است. ابتدا باید متد های singleton را در یک protocol قرار دهید. سپس اگر هنوز نیاز بود میتوانید از singleton به عنوان پیاده سازی پیشفرض استفاده کنید. اکنون میتوانید singleton یا object تغییر یافته را در جای مناسب تزریق کنید. در این روش کد شما میتواند به وسیله object های ماک (mock) شده از protocol تست شود. حتی با نادید گرفتن خود singleton.

typealias DataCompletionBlock = (Data?) -> Void

توابع مورد نیاز را حذف کنید:

protocol Session {
    func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock)
}

singleton خود را با protocol مطابقت دهید:

extension URLSession: Session {

    func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
        let task = self.dataTask(with: request) { data, _, _ in
            completionHandler(data)
        }
        task.resume()
    }
}

استفاده از تزریق وابستگی (dependency injection) با singleton:

class ApiService {

    var session: Session

    init(session: Session = URLSession.shared) {
        self.session = session
    }

    func load(_ request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
        self.session.make(request: request, completionHandler: completionHandler)
    }
}

یک object ماک ایجاد کنید:

class MockedSession: Session {

    func make(request: URLRequest, completionHandler: @escaping DataCompletionBlock) {
        completionHandler("Mocked data response".data(using: .utf8))
    }
}

تست های خود را بنویسید:

func test() {
    let api = ApiService(session: MockedSession())
    let request = URLRequest(url: URL(string: "https://localhost/")!)
    api.load(request) { data in
        print(String(data: data!, encoding: .utf8)!)
    }
}
test()

همانطور که دیدید پیاده سازی الگوی طراحی singleton بسیار ساده است ولی تصمیمی گیری در مورد استفاده از آن سخت است. این الگو قطعا ضدالگو نیست ولی اگر میخواهید از آن استفاده کنید باید مراقب باشد.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@armanabkar
آرمان
Frontend Developer

توسعه دهنده فرانت اند و موبایل

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید