الگوی طراحی Observer در #C: راهکاری برای اطلاع‌رسانی خودکار و ارتباط ماژولار

الگوی طراحی Observer (ناظر یا مشاهده‌گر) یکی از پرکاربردترین الگوها در مهندسی نرم‌افزار است که ذیل دسته‌بندی الگوهای رفتاری (Behavioral Patterns) قرار می‌گیرد. هدف اصلی این الگو ایجاد یک مکانیسم وابستگی یک-به-چند (One-to-Many Dependency) بین اشیاء است، به طوری که وقتی وضعیت یک شیء (به نام موضوع یا Subject) تغییر می‌کند، تمام اشیاء وابسته (به نام ناظر یا Observer) به طور خودکار و بدون نیاز به پرس‌وجوی مداوم، مطلع و به‌روزرسانی شوند.
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

الگوی طراحی Observer در #C: راهکاری برای اطلاع‌رسانی خودکار و ارتباط ماژولار

23 بازدید 0 نظر ۱۴۰۴/۰۸/۲۵

این الگو به طور ذاتی مفهوم کوپلینگ ضعیف (Loose Coupling) را تقویت می‌کند. به این معنی که اشیاء موضوع هیچ اطلاعات مستقیمی در مورد هویت یا ساختار اشیاء ناظر ندارند؛ آن‌ها صرفاً می‌دانند که باید به یک لیست از ناظران اطلاع‌رسانی کنند. این جداسازی، توسعه، نگهداری و انعطاف‌پذیری سیستم را به شدت افزایش می‌دهد.

در محیط #C و چارچوب دات‌نت، الگوی Observer به شکل‌های مختلفی پیاده‌سازی می‌شود که مهم‌ترین و رایج‌ترین آن‌ها استفاده از رویدادها (Events) و دلیگیت‌ها (Delegates) است.

 

ساختار و اجزای الگوی Observer

الگوی Observer شامل چهار جزء اصلی است که برای پیاده‌سازی مکانیسم اطلاع‌رسانی همکاری می‌کنند:

 

۱. Subject (موضوع) / Observable

این جزء شیئی است که وضعیت آن می‌تواند تغییر کند و ناظران علاقه‌مند به رصد این تغییرات هستند. وظایف اصلی آن عبارتند از:

  • حفظ وضعیت: نگهداری از داده‌هایی که تغییر می‌کنند.

  • مدیریت ناظران: دارای یک لیست از ناظران (Observerها) است و متدهایی برای ثبت (Attach/Subscribe) و حذف (Detach/Unsubscribe) ناظران ارائه می‌دهد.

  • اطلاع‌رسانی (Notify): متدی که در زمان تغییر وضعیت فراخوانی می‌شود و به تمام ناظران ثبت‌شده اطلاع می‌دهد.

 

۲. Observer (ناظر)

این جزء شیئی است که علاقه‌مند به دریافت اطلاعات تغییر وضعیت موضوع است. وظایف آن عبارتند از:

  • به‌روزرسانی (Update): پیاده‌سازی متدی که توسط موضوع فراخوانی می‌شود تا وضعیت جدید را دریافت کرده یا مستقیماً از موضوع بازیابی کند.

 

۳. ISubject (رابط موضوع)

یک رابط که قرارداد متدهای مدیریت ناظران و اطلاع‌رسانی را تعریف می‌کند. (در پیاده‌سازی C# معمولاً به صورت ضمنی توسط دلیگیت‌ها و رویدادها مدیریت می‌شود).

 

۴. IObserver (رابط ناظر)

یک رابط که قرارداد متد به‌روزرسانی را تعریف می‌کند. تمام ناظران بتنی باید این رابط را پیاده‌سازی کنند.

 

پیاده‌سازی Observer با استفاده از رابط‌ها در #C

یکی از روش‌های کلاسیک برای پیاده‌سازی این الگو، استفاده مستقیم از رابط‌ها است:

 

الف) تعریف رابط‌ها

// ISubject: قراردادی برای مدیریت ناظران و اطلاع‌رسانی
public interface ISubject
{
    void Attach(IObserver observer); // ثبت ناظر
    void Detach(IObserver observer); // حذف ناظر
    void Notify();                   // اطلاع‌رسانی
}

// IObserver: قراردادی برای به‌روزرسانی
public interface IObserver
{
    void Update(ISubject subject); // دریافت تغییرات از موضوع
}

 

ب) کلاس Subject (موضوع بتنی)

public class StockMarket : ISubject
{
    private List<IObserver> _observers = new List<IObserver>();
    private float _stockPrice;

    public float StockPrice
    {
        get { return _stockPrice; }
        set
        {
            if (_stockPrice != value)
            {
                _stockPrice = value;
                Console.WriteLine($"Price changed to: {_stockPrice}");
                Notify(); // اطلاع‌رسانی به ناظران
            }
        }
    }

    public void Attach(IObserver observer)
    {
        Console.WriteLine($"{observer.GetType().Name} subscribed.");
        _observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
        Console.WriteLine($"{observer.GetType().Name} unsubscribed.");
    }

    public void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.Update(this);
        }
    }
}

 

ج) کلاس Observer (ناظران بتنی)

public class Investor : IObserver
{
    private string _name;

    public Investor(string name)
    {
        _name = name;
    }

    public void Update(ISubject subject)
    {
        if (subject is StockMarket market)
        {
            Console.WriteLine($"Investor {_name}: Stock price is now {market.StockPrice}. Time to react!");
        }
    }
}

 

د) اجرای مثال

// ایجاد موضوع
var market = new StockMarket();

// ایجاد ناظران
var investor1 = new Investor("Alice");
var investor2 = new Investor("Bob");

// ثبت ناظران
market.Attach(investor1);
market.Attach(investor2);

// تغییر وضعیت موضوع - ناظران به طور خودکار مطلع می‌شوند
market.StockPrice = 105.50f;
market.StockPrice = 110.25f;

// حذف یک ناظر
market.Detach(investor1);

// تغییر مجدد وضعیت
market.StockPrice = 108.00f; // فقط باب مطلع می‌شود

 

روش رایج #C: الگو با استفاده از Events و Delegates

در دات‌نت، پیاده‌سازی الگوی Observer اغلب به شکل بهینه‌تر و بومی‌تری با استفاده از رویدادها (Events) انجام می‌شود. رویدادها در C# به طور ذاتی الگوی Observer را پیاده‌سازی می‌کنند:

  • Subject (کلاس صادرکننده رویداد) دارای یک Event است.

  • Observer (کلاس گیرنده رویداد) متد خود را به این رویداد Subscribe (مشترک) می‌کند.

  • وقتی رویداد فراخوانی (Raised) می‌شود، تمام متدهای مشترک‌شده به طور خودکار اجرا می‌شوند.

 

مزایای استفاده از Events:

  1. خوانایی بالا: سینتکس $+= برای اشتراک و $-= برای لغو اشتراک، کد را بسیار خواناتر می‌کند.

  2. امنیت (Encapsulation): رویدادها از بیرون کلاس فقط قابلیت $+= و $-= دارند و نمی‌توان آن‌ها را به صورت مستقیم فراخوانی کرد، در حالی که دلیگیت‌ها عمومی (Public) قابل فراخوانی مستقیم هستند.

  3. مدیریت حافظه خودکار: زبان C# و CLR مدیریت مناسب‌تری بر روی Delegateها و رفع نشتی حافظه دارند.

 

پیاده‌سازی با Events

// 1. تعریف دلیگیت (قرارداد متد Handler)
public delegate void PriceChangedEventHandler(object sender, float newPrice);

public class StockMarketEvent // Subject
{
    // 2. تعریف رویداد با استفاده از دلیگیت
    public event PriceChangedEventHandler PriceChanged;
    
    private float _stockPrice;

    public float StockPrice
    {
        get { return _stockPrice; }
        set
        {
            if (_stockPrice != value)
            {
                _stockPrice = value;
                // 3. فراخوانی رویداد (Notify)
                PriceChanged?.Invoke(this, _stockPrice);
            }
        }
    }
}

public class InvestorEvent // Observer
{
    private string _name;

    public InvestorEvent(string name)
    {
        _name = name;
    }

    // متد Handler که به رویداد وصل می‌شود
    public void HandlePriceChange(object sender, float newPrice) 
    {
        Console.WriteLine($"Investor {_name}: New price (via Event): {newPrice}.");
    }
}

 

// اجرای مثال با Events
var marketEvent = new StockMarketEvent();
var alice = new InvestorEvent("Alice");
var bob = new InvestorEvent("Bob");

// اشتراک (Subscribe)
marketEvent.PriceChanged += alice.HandlePriceChange;
marketEvent.PriceChanged += bob.HandlePriceChange;

marketEvent.StockPrice = 150.0f;

// لغو اشتراک (Unsubscribe)
marketEvent.PriceChanged -= alice.HandlePriceChange;

marketEvent.StockPrice = 145.0f; // فقط باب مطلع می‌شود

 

کاربردها و مزایای الگوی Observer

الگوی Observer یکی از پرکاربردترین الگوها در توسعه نرم‌افزار مدرن است و تقریباً در هر سیستم پیچیده‌ای یافت می‌شود:

 

۱. کاربردهای کلیدی:

  • رابط‌های کاربری گرافیکی (GUI): در هنگام کلیک روی یک دکمه (Subject)، این رویداد به طور خودکار چندین المان دیگر در صفحه (Observerها) مانند نوار وضعیت، یا یک پنل گزارش را به‌روزرسانی می‌کند.

  • معماری Model-View-Controller (MVC) / Model-View-ViewModel (MVVM): در این الگوها، Model (موضوع) داده‌ها را نگهداری می‌کند و View (ناظر) به تغییرات Model گوش می‌دهد تا بدون درگیری با منطق داده، ظاهر خود را به‌روز کند.

  • سامانه‌های خبررسانی و پیام‌رسانی: در پلتفرم‌های خبری، یک نویسنده (Subject) یک خبر را منتشر می‌کند و هزاران مشترک (Observer) به طور همزمان مطلع می‌شوند.

  • مانیتورینگ و لاگ‌گیری: یک شیء اصلی (مثلاً یک سرویس) در زمان وقوع خطا یا موفقیت (تغییر وضعیت)، به چندین ماژول لاگ‌گیر (Observerها) اطلاع می‌دهد تا رخداد را ثبت کنند.

 

۲. مزایای معماری:

  • کوپلینگ ضعیف (Loose Coupling): موضوع هیچ نیازی به دانستن جزئیات عملیاتی ناظران ندارد. این امر، امکان تغییر یا افزودن ناظران جدید بدون دستکاری کد موضوع را فراهم می‌کند.

  • انعطاف‌پذیری و مقیاس‌پذیری: به راحتی می‌توان ناظران جدیدی به سیستم اضافه یا حذف کرد. این کار باعث می‌شود سیستم به راحتی با نیازهای جدید سازگار شود.

  • قابلیت استفاده مجدد: موضوع و ناظران را می‌توان در بخش‌های مختلف برنامه که نیاز به ارتباط غیرمستقیم دارند، به کار گرفت.

  • پخش ناهمگام (Asynchronous Notification): اطلاع‌رسانی به ناظران می‌تواند به صورت ناهمگام انجام شود، که این امر مانع از مسدود شدن thread اصلی توسط عملیات طولانی در یک ناظر خاص می‌شود.

 

ملاحظات و چالش‌ها

مانند هر الگوی طراحی دیگری، Observer نیز چالش‌های خاص خود را دارد:

  1. ترتیب اطلاع‌رسانی: در پیاده‌سازی‌های رایج C# با Events، تضمینی برای ترتیب فراخوانی متدهای ناظران وجود ندارد. اگر ترتیب مهم باشد، باید مکانیسم داخلی Subject برای مدیریت لیست ناظران، این ترتیب را تضمین کند.

  2. نشتی حافظه (Memory Leaks): اگر ناظران از موضوع لغو اشتراک (Unsubscribe) نکنند، موضوع به مرجع ناظران ضعیف (Weak Reference) ادامه می‌دهد و مانع از جمع‌آوری زباله (Garbage Collection) آن‌ها می‌شود. این امر به خصوص در سناریوهای طولانی‌مدت (مانند برنامه‌های دسکتاپ) می‌تواند باعث نشتی حافظه شود. استفاده از Weak Events یکی از راه‌حل‌های این مشکل در دات‌نت است.

  3. پیچیدگی فراخوانی: اگر یک موضوع تعداد زیادی ناظر داشته باشد که هر کدام عملیات سنگینی انجام می‌دهند، فراخوانی متد Notify() ممکن است زمان زیادی ببرد و عملکرد سیستم را تحت تأثیر قرار دهد. راه‌حل این است که اطلاع‌رسانی در یک thread جداگانه (Asynchronously) انجام شود.

 

تکامل الگو: از Observer به Reactive Extensions (Rx)

در اکوسیستم دات‌نت، الگوی Observer به تدریج به سمت یک الگوی قدرتمندتر به نام Reactive Extensions (Rx) یا مشاهده‌پذیر واکنشی تکامل یافته است.

در Rx، مفاهیم IObservable (نسخه پیشرفته Subject) و IObserver (نسخه پیشرفته ناظر) پیاده‌سازی شده‌اند. تفاوت اصلی در این است که Rx جریان داده‌ها را مدیریت می‌کند و الگو را برای کار با توالی‌های ناهمگام داده‌ها، خطاها و تکمیل‌ها (Complete/Error/Next) گسترش می‌دهد، در حالی که الگوی Observer کلاسیک تنها برای اطلاع‌رسانی وضعیت استفاده می‌شود. Rx ابزاری قدرتمند برای برنامه‌نویسی واکنشی در دات‌نت است که تکامل منطقی الگوی Observer به شمار می‌آید.

 

جمع‌بندی

الگوی طراحی Observer یک ابزار حیاتی برای ایجاد نرم‌افزارهای ماژولار، انعطاف‌پذیر و با کوپلینگ ضعیف در C# است. چه با پیاده‌سازی کلاسیک رابط‌ها و چه با استفاده از قابلیت‌های بومی زبان C# نظیر Events و Delegates، این الگو تضمین می‌کند که تغییر وضعیت یک بخش سیستم به طور خودکار به سایر بخش‌های وابسته اطلاع‌رسانی شود، بدون اینکه وابستگی مستقیمی بین آن‌ها وجود داشته باشد. تسلط بر این الگو برای هر توسعه‌دهنده C# که قصد طراحی سیستم‌های بزرگ و مقیاس‌پذیر را دارد، ضروری است.

 

لینک استاندارد شده: 4D7

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه