الگوی طراحی Observer، وابستگی یک به چند را بین objectها تعریف می کند به طوری که وقتی یک object تغییر حالت می دهد، تمام وابسته های آن به طور خودکار مطلع می شوند.
یک sensor را در نظر بگیرید که در نقش subject، دمای یک قطعه سخت افزاری را بر می گرداند و قصد داریم خروجی آن را در observerهای مختلف به اشکال متفاوت نمایش دهیم.
بنابراین یک کلاس sensor برای این جریان ایجاد می کنیم.
public class Sensor { public int CurrentTemperature { get; private set; } public void StartDetecting() { Task.Factory.StartNew(() => { var random = new Random(); while (true) { CurrentTemperature = random.Next(-10, 50); } }); } }
قصد داریم بدون اینکه sensor به observerها وابستگی داشته باشد، در مورد تغییرات دما اطلاع داشته باشیم.
برای این کار نیاز به تعریف چند abstraction داریم که در دات نت به نام های IObservable و IObserver شناخته می شوند. observer کسی است که notify می شود و برای اطلاع دادن تغییرات، به یک observable ارسال می شود.
public interface IObserver<T> { void Notify(T value); } public interface IObservable<T> { void Subscribe(IObserver<T> observer); }
دو کلاس observer ایجاد می کنیم که در لحظه تغییر دما، مقدار را به طریقی نمایش می دهند.
public class NumericDisplay : IObserver<int> { public void Notify(int value) { Console.WriteLine(value); } } public class Gauge : IObserver<int> { public void Notify(int value) { Debug.WriteLine($"------{value}------"); } }
در کلاس sensor که یک observable است، observerها می تونند از طریق متد Subscribe به آن وصل شوند و زمانی که تغییر اتفاق می افتد، observable آن ها را notify می کند.
public class Sensor : IObservable<int> { private IList<IObserver<int>> _observers = new List<IObserver<int>>(); public void Subscribe(IObserver<int> observer) { _observers.Add(observer); } public int CurrentTemperature { get; private set; } public void StartDetecting() { Task.Factory.StartNew(() => { var random = new Random(); while (true) { CurrentTemperature = random.Next(-10, 50); foreach (var observer in _observers) { observer.Notify(CurrentTemperature); } } }); } }
اتفاقی که می افتد، decoupling بین observable و observerها می باشد. subject اتفاق را به بقیه خبر می دهد ولی در مورد observerها اطلاعی ندارد.
var sensor = new Sensor(); sensor.Subscribe(new NumericDisplay()); sensor.Subscribe(new Gauge()); sensor.StartDetecting(); Console.ReadLine();
دو مفهوم Push Model و Pull Model در پیاده سازی observer وجود دارد که در مورد پروتکل بین observable و observer صحبت می کند.
با فرض داشتن دو کامپوننت A و B، دو رویکرد کلی می تونیم در مورد ارتباط بین آن ها داشته باشیم. اگر A را به عنوان subject در نظر بگیریم، یک رویکرد این است که تغییرات از آن push شود و رویکرد دیگر این است که B به عنوان observer، از طریق pull کردن به داده ها برسد.
در الگوی observer نیز دو نوع پیاده سازی push model و pull model وجود دارد. رویکردی که در مثال بالا ملاحظه کردید از نوع push model بود. به این صورت که subject عنوان می کند که این تغییر اتفاق افتاده است.
در push model، بخش subject داده بیشتری در مورد تغییرات دارد. به این صورت که observerهایی هستند که مشخصا به این مقدار نیاز دارند. بنابراین در pull model، وابستگی subject به dependencyها کمتر است.
موضوعی که وجود دارد، در push model مفهوم reusability را از دست می دهیم. در مثال بالا فقط یک عدد را اطلاع رسانی می کنیم و ابعاد دیگری از آن نمی توانیم داشته باشیم. در pull model، فهمیدن اینکه چه چیزی تغییر کرده است، گاهی می تواند مساله ایجاد کند.
در استفاده از push model با توجه به مساله، وجود dependency به observerها می تواند مشکل ساز شود. در مساله sensor این موضوع وجود ندارد. به این دلیل که ماهیت sensor یک عدد است.
در مورد pull model، می تونیم کل object را به observerها notify کنیم و observerها تصمیم می گیرند که چه دیتایی را از آن بخوانند.
به طور خلاصه، observerها در اینجا variability هستند که نسبت به تغییرات reaction نشان می دهند. و change notification و subject که به عنوان پروتوکل است نیز commonality ما می باشد.