Skip to content

Observer

Montazar edited this page Aug 9, 2022 · 2 revisions

الگو طراحی Observer

image

الگوی Observer یک الگوی طراحی رفتاری (Behavioral) است که به ما امکان می دهد مکانیزمی داشته باشیم، برای اطلاع رسانی تغییرات یک آبجکت به چندین آبجکت دیگر.

به عبارت دیگر هر گاه آبجکتی توسط آبجکت های دیگر مورد مشاهده (Observation) قرار گیرد، در صورت ایجاد تغییر در آن آبجکت، آبجکت های مشاهده کننده می‌توانند از آن تغییرات مطلع شوند.

مسئله

تصور کنید دو نوع آبجکت داریم: مشتری و فروشگاه. و مشتری علاقه زیادی به مارک خاصی از محصول داره (مثلاً مدل جدیدی از آیفون) که به زودی قراره در فروشگاه موجود باشه و فروخته بشه.

مشتری می تواند هر روز به فروشگاه مراجعه کند و در دسترس بودن محصول را بررسی کند. اما مادامی که محصول هنوز به فروشگاه نرسیده است، بیشتر این مراجعه ها بی فایده هستند.

image

از طرفی، فروشگاه می تواند هر بار كه محصول جدیدی در دسترس است ، تعداد زیادی ایمیل (كه احتمالاً اسپم محسوب می شوند) را برای همه مشتریان ارسال كند. این باعث می شود برخی از مشتریان از مراجعه بی پایان به فروشگاه نجات پیدا کنند. اما در عین حال ، این باعث ناراحتی سایر مشتریانی می‌شود که علاقه ای به اطلاع یافتن از محصولات جدید ندارند.

به نظر می رسد اینجا یک تضاد داشته باشیم.. یا مشتری وقت خود را برای بررسی در دسترس بودن محصول هدر می دهد و یا فروشگاه منابع خود را برای اطلاع رسانی به مشتریانِ اشتباه هدر می دهد..

راه حل

آبجکتی که دارای اِستیتی (وضعیت) است که به آن علاقه مندیم غالباً سوژه نامیده می شود، اما از آنجا که این آبجکت قصد دارد سایر آبجکت ها را نیز در مورد تغییرات وضعیت خود آگاه سازد، آنرا ناشر و یا (Publisher) می نامیم. همه آبجکت های دیگری که می خواهند تغییرات مورد نظر در وضعیت ناشر را پیگیری کنند، مشترک و یا (Subscriber) نامیده می شوند.

الگوی Observer پیشنهاد می کند که شما یک مکانیزم اشتراک را به کلاس ناشر اضافه کنید تا اشیا به صورت جداگانه بتوانند، در جریان رویداد های ناشی از آن ناشر مشترک شوند و یا اشتراک خود را لغو کنند.

مسئله آنطور که به نظر می رسد پیچیده نیست; در حقیقت این مکانیزم شامل موارد زیر است:

  • یک آرایه که لیست آبجکت های مشترک را ذخیره می‌کند
  • چند متد پابلیک که اجازه می‌دهند، مشترکین را اضافه و یا حذف کرد

image

اکنون، هر زمان که رویدادی مهم برای ناشر رخ دهد، مشترکان خود را بررسی می‌کند و متد اطلاع رسانی خاصی را برای آن آبجکت ها فراخوانی می کند.

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

این اینترفیس باید متدی را برای اطلاع رسانی به همراه مجموعه ای از پارامترها مشخص کند، تا ناشر بتواند در کنار اعلان تغییرات، یک سری داده را نیز پاس بدهد.

image

اگر برنامه شما چندین نوع ناشر مختلف دارد و می خواهید مشترکان را با همه آنها سازگار کنید ، می توانید از این هم فراتر رفته و همه ناشران را مجبور کنید تا از یک اینترفیس تبعیت کنند. این اینترفیس تنها باید یک سری متد ‌های مربوط به مشترکان را اعمال کند. این اینترفیس به مشترکان اجازه می دهد تا بدون وابستگی به کلاس های ثابت (concrete) از وضعیت ناشران مطلع شوند.

قیاس در دنیای واقعی

image

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

همچنین شما به عنوان یک مشترک هر زمان که خواستید میتوانید اشتراک خود را لغو کنید، تا مجله یا روزنامه ای برای شما ارسال نشود.

ساختار

image

۱) ناشر رویدادهای مورد علاقه سایر آبجکت ها را صادر می کند. این رویدادها زمانی اتفاق می افتند که ناشر وضعیت خود را تغییر دهد یا برخی رفتارها را انجام دهد. همچنین ناشر دارای زیرساختی است که اجازه می‌دهد: مشترکان جدید به لیست اضافه شده و یا مشترکین فعلی از لیست حذف شوند.

۲) هنگامی که یک رویداد جدید اتفاق می افتد ، ناشر لیست مشترکان را پیمایش می کند و متد اطلاع رسانی ای که در اینترفیس مشترکان مشخص شده است، را روی آبجکت مشترکان فراخوانی می کند.

۳) اینترفیس مشترکان یک رابط اطلاع رسانی را اعمال می‌کند; که اکثر مواقع یک متد update ساده است که میتواند پارامترهایی را هم بپذیرد و به ناشر اجازه دهد تا جزئیات رویداد را هم انتقال دهد.

۴) مشترکان برخی اقدامات را در پاسخ به اعلان های صادر شده توسط ناشر انجام می دهند. همه این مشترکان باید از یک اینترفیس تبعیت کنند; تا از در هم تنیده شدن ناشر و کلاسهای concrete جلوگیری شود.

۵) معمولا مشترکان برای هندل کردن پروسه آپدیت به شکلی صحیح ، به یک سری داده نیاز دارند. به همین دلیل ناشران اغلب برخی از داده ها را به عنوان آرگومان های متد اطلاع رسانی (notify) منتقل می کنند. در صورت نیاز ناشر می‌تواند خودش را نیز به عنوان آرگومان پاس داده و به مشترک اجازه دهد تا داده های مورد نیازش را مستقیما واکشی کند.

۶) در نهایت آبجکت های ناشر و مشترکان را جداگانه می‌سازیم و سپس لیست مشترکان را برای دریافت آپدیت های آن ناشر ثبت می‌کنیم.

کاربرد ها

۱) هنگامی که تغییر در وضعیت یک شئ، نیاز به تغییرات در سایر اشیا داشته باشد و یا تغییرات به صورت داینامیک صورت بگیرند، می‌توانید از این الگو استفاده کنید.

۲) الگوی Observer به هر آبجکتی که از اینترفیس Subscriber تبعیت کند، اجازه می‌دهد تا رویدادهای آبجکت Publisher را دنبال کند.

مزایا و معایب

  • قاعده باز و بسته (Open/Closed Principle) رعایت می‌شود. (می‌توانید کلاس های مشترکان جدیدی به سیستم اضافه کنید، بدون اینکه لازم باشد، در کدهای ناشر تغییری ایجاد کنید)
  • می‌توانید در حین Runtime بین اشیا رابطه ایجاد کنید
  • متدهای notify کلاس های مشترکان به صورت تصادفی فراخوانی می‌شود
  • در صورت روزرسانی جزء کوچکی از آبجکت تمام مشترکان مجددا notify میشوند

مثال با کد JAVA

public class NewsAgency {
    private String news;
    private List<Channel> channels = new ArrayList<>();

    public void addObserver(Channel channel) {
        this.channels.add(channel);
    }

    public void removeObserver(Channel channel) {
        this.channels.remove(channel);
    }

    public void setNews(String news) {
        this.news = news;
        for (Channel channel : this.channels) {
            channel.update(this.news);
        }
    }
}
public interface Channel {
    public void update(Object o);
}
public class NewsChannel implements Channel {
    private String news;

    @Override
    public void update(Object news) {
        this.setNews((String) news);
    } 
}
NewsAgency observable = new NewsAgency();
NewsChannel observer = new NewsChannel();

observable.addObserver(observer);
observable.setNews("news");

مثال TypeScript

interface Subject {
    attach(observer: Observer): void;

    detach(observer: Observer): void;

    notify(): void;
}
class ConcreteSubject implements Subject {

    public state: number;

    private observers: Observer[] = [];

    public attach(observer: Observer): void {
        const isExist = this.observers.includes(observer);
        if (isExist) {
            return console.log('Subject: Observer has been attached already.');
        }

        console.log('Subject: Attached an observer.');
        this.observers.push(observer);
    }

    public detach(observer: Observer): void {
        const observerIndex = this.observers.indexOf(observer);
        if (observerIndex === -1) {
            return console.log('Subject: Nonexistent observer.');
        }

        this.observers.splice(observerIndex, 1);
        console.log('Subject: Detached an observer.');
    }

    public notify(): void {
        console.log('Subject: Notifying observers...');
        for (const observer of this.observers) {
            observer.update(this);
        }
    }


    public someBusinessLogic(): void {
        console.log('\nSubject: I\'m doing something important.');
        this.state = Math.floor(Math.random() * (10 + 1));

        console.log(`Subject: My state has just changed to: ${this.state}`);
        this.notify();
    }
}
interface Observer {
    update(subject: Subject): void;
}
class ConcreteObserverA implements Observer {
    public update(subject: Subject): void {
        if (subject instanceof ConcreteSubject && subject.state < 3) {
            console.log('ConcreteObserverA: Reacted to the event.');
        }
    }
}
class ConcreteObserverB implements Observer {
    public update(subject: Subject): void {
        if (subject instanceof ConcreteSubject && (subject.state === 0 || subject.state >= 2)) {
            console.log('ConcreteObserverB: Reacted to the event.');
        }
    }
}
const subject = new ConcreteSubject();

const observer1 = new ConcreteObserverA();
subject.attach(observer1);

const observer2 = new ConcreteObserverB();
subject.attach(observer2);

subject.someBusinessLogic();
subject.someBusinessLogic();

subject.detach(observer2);

subject.someBusinessLogic();

OutPut

Subject: Attached an observer.
Subject: Attached an observer.

Subject: I'm doing something important.
Subject: My state has just changed to: 6
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
ConcreteObserverB: Reacted to the event.

Subject: I'm doing something important.
Subject: My state has just changed to: 1
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
ConcreteObserverB: Reacted to the event.
Subject: Detached an observer.

Subject: I'm doing something important.
Subject: My state has just changed to: 5
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.

منابع

https://refactoring.guru/design-patterns/observer https://refactoring.guru/design-patterns/observer/typescript/example#example-0

Is necessary

Design Pattern

Creational

Structural

Behavioral

Template

Clone this wiki locally