觀察者模式-訂閱釋出模式

LiuWango發表於2021-03-30

觀察者模式(Observer Pattern)

觀察者模式是一種物件間的一對多依賴關係,當一個物件的狀態發生變化時,所有依賴他的物件都將得到通知。通常運用在物件之間的訊息通訊中。

// 比如現在有一群學生,可以組成小組,可以尋求幫助

class Students {
    constructor(name, level) {
        this.name = name;
        this.level = level;
        this.tasks = [];
    }

    askForHelp(subject) {
        console.log(`關於${subject}, ${this.level}${this.name}向大家尋求幫助!`)
        this.tasks.forEach((fn) => {
            fn(subject);
        });
    }
    
    makeTeam(student, fn) {
        console.log(`${this.level}${this.name}與${student.level}${student.name}組成了學習小組!`);
        student.tasks.push(fn);
    }
}

const studentWango = new Students('Wango', '學神');
const studentLily = new Students('Lily', '學霸');
const studentTom = new Students('Tom', '平平無奇的');
const studentPeter = new Students('Peter', '學渣');

// 老師說Peter的成績比較差,所以大家都和Peter組成了學習小組
studentWango.makeTeam(studentPeter, function(subject) {
    console.log(`Wango說:${subject}確實很難,我也花了好幾分鐘複習才堪堪得到了滿分!`);
})
// 學神Wango與學渣Peter組成了學習小組!

studentLily.makeTeam(studentPeter, function(subject) {
    console.log(`Lily說:${subject}!? 背就完事了!`);
})
// 學霸Lily與學渣Peter組成了學習小組!

studentTom.makeTeam(studentPeter, function(subject) {
    console.log(`Tom說:${subject}的話,一定要好好學,但學不好也沒事,我們都是凡人!`);
})
// 平平無奇的Tom與學渣Peter組成了學習小組!

// 有一天...
studentPeter.askForHelp('英語');
// 關於英語, 學渣Peter向大家尋求幫助!
// Wango說:英語確實很難,我也花了好幾分鐘複習才堪堪得到了滿分!
// Lily說:英語!? 背就完事了!
// Tom說:英語的話,一定要好好學,但學不好也沒事,我們都是凡人!

釋出訂閱模式(Pub-Sub Pattern)

釋出訂閱模並不在24種設計模式之中,只是觀察者模式的一種變體。相比觀察者模式,訂閱釋出模式新增了一個任務排程中心,訂閱者將自己想要訂閱的事件註冊到排程中心,當釋出者釋出該事件到排程中心,也就是觸發該事件時,由排程中心同一排程訂閱者註冊到排程中心的程式碼。

// 同樣的學習小組

class Students {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }
    // 訂閱事件
    on(type, fn) {
        ClassRoom.subscribe(type, fn);
    }
    // 釋出內容
    emit(type, content) {
        ClassRoom.askForHelp(type, content);
    }
}
// 任務排程中心
class ClassRoom {
    static topic = Object.create(null)
    // 對於某個事件釋出任務
    static askForHelp(type, content) {
        console.log(`${type}:${content}`);
        if (!ClassRoom.topic[type] || ClassRoom.topic[type].length === 0) {
            console.log(`然而並沒有學生對${type}感興趣!`);
            return;
        }
        ClassRoom.topic[type].forEach((fn) => {
            fn(content);
        });
    }
    // 訂閱某個型別的事件
    static subscribe(type, fn) {
        if (!ClassRoom.topic[type]) {
            ClassRoom.topic[type] = [];
        }
        console.log(`有學生訂閱了${type}.`);
        ClassRoom.topic[type].push(fn);
    }
}

const studentWango = new Students('Wango', '學神');
const studentLily = new Students('Lily', '學霸');
const studentTom = new Students('Tom', '平平無奇的');
const studentPeter = new Students('Peter', '學渣');

studentWango.on('Life', function(type, content) {
    console.log(`Wango說: 終於有人問生活上的問題了,果然還是做個凡人比較快樂啊!`);
})
// 有學生訂閱了Life.

studentWango.on('English', function(type, content) {
    console.log(`Wango說:英語確實很難,我也花了好幾分鐘複習才堪堪得到了滿分!`);
})
// 有學生訂閱了English.

studentTom.on('English', function(type, content) {
    console.log(`Tom說:英語的話,一定要好好學,但學不好也沒事,我們都是凡人!`);
})
// 有學生訂閱了English.

studentLily.on('English', function(type, content) {
    console.log(`Lily說: 英語?! 背就完事了!`);
})
// 有學生訂閱了English.

studentLily.emit('Life', '這個世界有英雄嗎?');
// Life:這個世界有英雄嗎?   
// Wango說: 終於有人問生活上的問題了,果然還是做個凡人比較快樂啊!

studentTom.emit('English', '單詞記不住怎麼辦啊?');
// English:單詞記不住怎麼辦啊?
// Wango說:英語確實很難,我也花了好幾分鐘複習才堪堪得到了滿分!
// Tom說:英語的話,一定要好好學,但學不好也沒事,我們都是凡人!
// Lily說: 英語?! 背就完事了!

studentPeter.emit('Game', '學什麼?玩遊戲啊!');
// Game:學什麼?玩遊戲啊!
// 然而並沒有學生對Game感興趣!

區別與聯絡

相比觀察者模,訂閱釋出模式中的的訂閱者和釋出者分離更加徹底。訂閱者在訂閱的時候只關注事件本身,而不用關心是誰釋出的資訊;而釋出者同樣只需要關注釋出的內容,不用關心是誰在訂閱,如何處理。

總的來說,儘管訂閱釋出模式多了個排程中心,但使用起來可以更加靈活,對多事件的支援更好,當然,記憶體消耗更大,畢竟要為維護多個事件佇列。

相關文章