عنوان:

‫مدیریت عملیات آسنکرون در سازنده‌های #C: الگوی متد کارخانه آسنکرون (Async Factory Method)


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۰۸ ۰۸:۱۰
آدرس: www.dntips.ir
برنامه‌نویسی آسنکرون (asynchronous programming) نقشی حیاتی در ایجاد برنامه‌های پاسخ‌گو (responsive) و کارآمد ایفا می‌کند. با این حال، توسعه‌دهندگان گاهی با محدودیت‌هایی در استفاده از این قابلیت‌ها در بخش‌های خاصی از کد، مانند سازنده‌های کلاس، مواجه می‌شوند. این مقاله به بررسی این چالش و ارائه یک راه‌حل استاندارد و مؤثر با استفاده از الگوی متد کارخانه آسنکرون می‌پردازد.

مقدمه: چالش آسنکرونی در مقداردهی اولیه اشیاء
هنگام ایجاد یک نمونه (instance) از یک کلاس، سازنده (constructor) اولین قطعه کدی است که اجرا می‌شود و وظیفه مقداردهی اولیه فیلدها و آماده‌سازی شیء برای استفاده را بر عهده دارد. گاهی اوقات، فرآیند مقداردهی اولیه نیازمند انجام عملیاتی است که ذاتاً آسنکرون هستند، مانند خواندن اطلاعات از یک فایل، برقراری ارتباط با یک سرویس تحت شبکه، یا اجرای یک وظیفه زمان‌بر بدون مسدود کردن نخ اصلی برنامه (main thread).
فرض کنید کلاسی به نام Spy داریم که اطلاعات اولیه یک جاسوس را نگهداری می‌کند:
public sealed class Spy
{
  public string FirstName { get; }
  public string Surname { get; }

  public Spy(string firstName, string surname)
  {
    FirstName = firstName;
    Surname = surname;
  }
}
در این حالت، سازنده به سادگی مقادیر FirstName و Surname را تنظیم می‌کند. حال، فرض کنید می‌خواهیم هنگام ساخت یک شیء Spy، یک موسیقی متن نیز به صورت آسنکرون پخش شود. برای این منظور، متد PlaySong را به صورت زیر تعریف می‌کنیم:
// Play theme song here
public async Task PlaySong()
{
    await Task.Delay(TimeSpan.FromSeconds(5)); // شبیه‌سازی یک عملیات زمان‌بر
    Console.WriteLine("Playing that song!");
}
این متد با استفاده از async و await، یک عملیات آسنکرون را شبیه‌سازی می‌کند که ۵ ثانیه طول می‌کشد.

مشکل کجاست؟ محدودیت سازنده‌ها
اگر تلاش کنیم متد PlaySong را مستقیماً از درون سازنده فراخوانی و await کنیم، با خطا مواجه خواهیم شد:
public Spy(string firstName, string surname)
{
    FirstName = firstName;
    Surname = surname;
    await PlaySong(); // این خط باعث بروز خطا می‌شود
}
کامپایلر #C خطاهای زیر را گزارش خواهد داد:
error CS4033: The 'await' operator can only be used within an async method. 
Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
error CS4008: Cannot await 'void'.
دلیل بروز خطا:
۱. عملگر await تنها در متدهایی قابل استفاده است که با کلمه کلیدی async مشخص شده باشند.
۲. سازنده‌ها در #C نمی‌توانند با کلمه کلیدی async مشخص شوند. دلیل این امر آن است که سازنده‌ها به طور ضمنی یک نمونه از کلاس خود را برمی‌گردانند و نوع بازگشتی آن‌ها قابل تغییر به Task یا Task<T> نیست، که این نوع بازگشتی برای متدهای async الزامی است. سازنده باید به طور همزمان (synchronously) شیء را برگرداند.

راه‌حل: الگوی متد کارخانه آسنکرون (Async Factory Method Pattern)
یک راه‌حل زیبا و متداول برای این مشکل، استفاده از الگوی "متد کارخانه آسنکرون" است. این الگو شامل چند مرحله کلیدی است:
  • خصوصی کردن سازنده (Making the Constructor Private): سازنده کلاس را به private تغییر می‌دهیم تا از ایجاد مستقیم نمونه کلاس از خارج آن جلوگیری شود. این کار تضمین می‌کند که تنها راه ایجاد شیء، از طریق متد کارخانه خواهد بود.
  • ایجاد یک متد کارخانه ایستا و آسنکرون (Creating a Static Async Factory Method): یک متد public static async Task ایجاد می‌کنیم. این متد وظیفه ایجاد نمونه و انجام عملیات آسنکرون مورد نیاز برای مقداردهی اولیه را بر عهده خواهد داشت.
با اعمال این الگو بر روی کلاس Spy، کد به شکل زیر تغییر می‌کند:
public sealed class Spy
{
    public string FirstName { get; }
    public string Surname { get; }

    // 1. سازنده به private تغییر یافته است
    private Spy(string firstName, string surname)
    {
        FirstName = firstName;
        Surname = surname;
    }

    // متد آسنکرون برای پخش موسیقی
    public async Task PlaySong()
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine("Playing that song!");
    }

    /// <summary>
    /// Factory method to return a spy
    /// </summary>
    /// <param name="firstName"></param>
    /// <param name="surname"></param>
    /// <returns>یک Task که نتیجه آن شیء Spy کاملاً مقداردهی شده است</returns>
    // 2. متد کارخانه ایستا و آسنکرون
    public static async Task<Spy> CreateSpy(string firstName, string surname)
    {
        // فراخوانی سازنده خصوصی برای ایجاد نمونه اولیه شیء
        var spy = new Spy(firstName, surname);
        // فراخوانی و انتظار برای تکمیل متد آسنکرون
        await spy.PlaySong();
        // بازگرداندن شیء کاملاً مقداردهی شده
        return spy;
    }
}
نکات کلیدی در راه‌حل فوق:
  • سازنده خصوصی (private Spy): این سازنده دیگر از خارج کلاس قابل فراخوانی نیست و تنها توسط متد کارخانه (CreateSpy) استفاده می‌شود. این سازنده همچنان وظیفه مقداردهی اولیه همزمان (synchronous) فیلدهای اصلی را بر عهده دارد.
  • متد کارخانه (CreateSpy):
  • این متد static است، بنابراین می‌توان آن را بدون نیاز به ایجاد نمونه‌ای از کلاس Spy فراخوانی کرد (مثلاً Spy.CreateSpy(...)).
  • این متد async است و نوع بازگشتی آن Task می‌باشد. این بدین معناست که متد، یک "وعده" (promise) برای تحویل یک شیء Spy در آینده را برمی‌گرداند.
  • درون این متد، ابتدا نمونه Spy با استفاده از سازنده خصوصی ایجاد می‌شود (var spy = new Spy(firstName, surname);).
  • سپس، متد آسنکرون PlaySong بر روی این نمونه فراخوانی شده و با استفاده از await، اجرای متد کارخانه تا زمان تکمیل PlaySong متوقف می‌شود (await spy.PlaySong();).
  • در نهایت، نمونه کاملاً مقداردهی شده و آماده spy بازگردانده می‌شود.

نحوه استفاده از متد کارخانه آسنکرون
برای ایجاد و مقداردهی اولیه یک شیء Spy با استفاده از این الگو، کد زیر را به کار می‌بریم:
// Create the spy asynchronously
var jamesBond = await Spy.CreateSpy("James", "Bond");
// Use the spy
Console.WriteLine($"Spy was created: {jamesBond.FirstName} {jamesBond.Surname}");
خروجی این کد به صورت زیر خواهد بود (پس از ۵ ثانیه تاخیر):
Playing that song!
Spy was created: James Bond
همانطور که مشاهده می‌شود، ابتدا پیام "Playing that song!" چاپ شده و سپس پیام ایجاد جاسوس نمایش داده می‌شود. این نشان می‌دهد که عملیات آسنکرون قبل از استفاده از شیء jamesBond به طور کامل انجام شده است.

مزایا و ملاحظات
استفاده از الگوی متد کارخانه آسنکرون چندین مزیت دارد:
  • جداسازی مسئولیت‌ها (Separation of Concerns): مسئولیت ایجاد شیء و مسئولیت انجام عملیات آسنکرون مقداردهی اولیه از هم جدا می‌شوند. سازنده همچنان ساده و همزمان باقی می‌ماند.
  • کد خواناتر و قابل نگهداری‌تر: منطق آسنکرون به طور واضح در متد کارخانه قرار می‌گیرد.
  • تضمین مقداردهی کامل: شیء تنها پس از تکمیل تمام عملیات آسنکرون مورد نیاز، به کد فراخواننده تحویل داده می‌شود.
  • انعطاف‌پذیری: این الگو به راحتی برای سناریوهای پیچیده‌تر مقداردهی اولیه که شامل چندین فراخوانی آسنکرون هستند، قابل گسترش است.
این الگو به ویژه زمانی مفید است که با کتابخانه‌هایی کار می‌کنید که تنها نسخه‌های آسنکرون از متدهای خود را ارائه می‌دهند (مانند برخی از کتابخانه‌های AWS SDK برای کار با سرویس‌هایی مثل Amazon S3) و شما نیاز دارید این عملیات را به عنوان بخشی از فرآیند ایجاد و مقداردهی اولیه شیء خود انجام دهید.

نتیجه‌گیری
اگرچه زبان #C به طور مستقیم اجازه تعریف سازنده‌های async را نمی‌دهد، اما این محدودیت به معنای عدم امکان انجام عملیات آسنکرون در طول فرآیند مقداردهی اولیه اشیاء نیست. الگوی متد کارخانه آسنکرون، با خصوصی کردن سازنده و معرفی یک متد ایستا و آسنکرون برای ایجاد و آماده‌سازی شیء، یک راه‌حل قدرتمند، تمیز و استاندارد برای این چالش ارائه می‌دهد. این رویکرد به توسعه‌دهندگان دات‌نت امکان می‌دهد تا برنامه‌هایی بنویسند که هم از قدرت برنامه‌نویسی آسنکرون بهره‌مند شوند و هم ساختار کد خود را منظم و قابل فهم نگه دارند. با استفاده صحیح از این الگو، می‌توان اشیائی را ایجاد کرد که قبل از استفاده، به طور کامل و صحیح، حتی با وابستگی به عملیات آسنکرون، مقداردهی اولیه شده‌اند.


مشاهده مطلب اصلی