觀察者模式

前言

觀察者(Observer)模式是一種使用頻率很高的設(shè)計(jì)模式,之前在介紹Javax.Servlet包下的Listener的時(shí)候,我們知道Listener能夠在某一特定事件發(fā)生的時(shí)候(比如Servlet容器的創(chuàng)建和銷毀)作出響應(yīng)。為了完成這個(gè)功能,Listener就采用了觀察者模式。

場(chǎng)景

假設(shè)我們現(xiàn)在有一個(gè)鬧鐘類Clock,當(dāng)把它開(kāi)啟的時(shí)候它就會(huì)在一個(gè)特定的時(shí)間鬧鈴,如果類Son聽(tīng)到這個(gè)鬧鐘的鈴聲,就會(huì)起床去上學(xué)。為了模擬這個(gè)場(chǎng)景,我們可以簡(jiǎn)單的寫下如下代碼。

  • ClockSon
class Clock {
    private volatile boolean ring;
    public void turnOn() {
        System.out.println("鬧鐘響了");
        ring = true;
    }
    public boolean isRing(){
        return ring;
    }
}
class Son {
    public void goToSchool(){
        System.out.println("孩子起床上學(xué)");
    }
}
  • 測(cè)試類
public class ObserverTest {
    public static Clock clock = new Clock();
    public static void main(String[] args) {
        Son son = new Son();

        new Thread(()->{
            try{
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clock.turnOn();
        }).start();

        while (!clock.isRing()) {
            // wait
        }
        son.goToSchool();
    }
}
  • 實(shí)驗(yàn)結(jié)果
鬧鐘響了
孩子起床上學(xué)

Process finished with exit code 0

這個(gè)實(shí)現(xiàn)方式很簡(jiǎn)單,Son一直輪詢判斷鬧鐘有沒(méi)有響就可以了。缺點(diǎn)也很顯然,淪陷的時(shí)候一直在空轉(zhuǎn),占用cpu時(shí)間,造成了資源浪費(fèi)。那有什么辦法可以不用空轉(zhuǎn)去輪詢狀態(tài)呢?我們可以讓鬧鐘鬧鈴的時(shí)候直接通知Son起來(lái)處理就好了,也就是讓son監(jiān)聽(tīng)clock轉(zhuǎn)換為clock通知son。

  • 優(yōu)化-取消輪詢
class Clock {
    private volatile boolean ring;
    private Son son = new Son();
    public void turnOn() {
        System.out.println("鬧鐘響了");
        ring = true;
        son.goToSchool();
    }
    public boolean isRing(){
        return ring;
    }
}

現(xiàn)在我們把son捆綁在clock上,當(dāng)clockturn on方法一調(diào)用就立刻調(diào)用songoToSchool()方法。現(xiàn)在可以說(shuō)我們把son注冊(cè)進(jìn)了clock,使其成為clock的觀察者。觀察者模式到這里就結(jié)束了嗎? 顯然沒(méi)有,考慮到我們對(duì)于一個(gè)事件并不是只有一個(gè)觀察者,假設(shè)在這個(gè)場(chǎng)景下,鬧鐘一響,不僅兒子要起床上學(xué),父親也要起床上班,母親則需要出門買菜,也就是說(shuō)對(duì)于鬧鐘鬧鈴這個(gè)事件,有3個(gè)觀察者,則上述代碼要改成如下。

  • 添加的Father,Mather
class Father {
    public void goToWork(){
        System.out.println("爸爸起床上班");
    }
}
class Mother {
    public void goBuyFood(){
        System.out.println("媽媽出門買菜");
    }
}
class Son {
    public void goToSchool(){
        System.out.println("孩子起床上學(xué)");
    }
}
  • Clock
class Clock {
    private volatile boolean ring;
    private Son son = new Son();
    private Father father = new Father();
    private Mother mother = new Mother();
    public void turnOn() {
        System.out.println("鬧鐘響了");
        ring = true;
        son.goToSchool();
        father.goToWork();
        mother.goBuyFood();
    }
    public boolean isRing(){
        return ring;
    }
}

添加了FatherMother類之后,我們還要把它們寫死進(jìn)Clock代碼里,此外還要明確每個(gè)觀察者需要被執(zhí)行的方法。所有的觀察者和Clock這個(gè)被觀察對(duì)象緊緊的耦合在一起了。代碼的擴(kuò)展性特別差。何如修改才能讓其真正成為觀察者模式呢?我們知道約定優(yōu)于配置,既然我們不知道每一個(gè)觀察者需要被調(diào)用的具體方法,那我們就約定一個(gè)方法叫做doSomething(),所有的觀察者只要實(shí)現(xiàn)某個(gè)接口實(shí)現(xiàn)該方法就可以了,此外有了這個(gè)接口,我們可以將所有的觀察者對(duì)象看成一類,在被觀察對(duì)象類中提供一個(gè)注冊(cè)接口就可以了。讓我們按照這個(gè)思路來(lái)進(jìn)行優(yōu)化。

  • 約定Observer接口
interface Observer {
    public void doSomething();
}

  • Son、MotherFather類都來(lái)實(shí)現(xiàn)這個(gè)接口
class Father implements Observer{
    public void goToWork(){
        System.out.println("爸爸起床上班");
    }

    @Override
    public void doSomething() {
        this.goToWork();
    }
}

class Mother implements Observer {
    public void goBuyFood(){
        System.out.println("媽媽出門買菜");
    }

    @Override
    public void doSomething() {
        goBuyFood();
    }
}
class Son implements Observer{
    public void goToSchool(){
        System.out.println("孩子起床上學(xué)");
    }

    @Override
    public void doSomething() {

    }
}
  • 修改Clock類提供添加觀察者方法
class Clock {
    private volatile boolean ring;
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer observer){
        observers.add(observer);
    }
    public void turnOn() {
        System.out.println("鬧鐘響了");
        ring = true;
        for (Observer observer : observers) {
            observer.doSomething();
        }
    }
    public boolean isRing(){
        return ring;
    }
}
  • 進(jìn)行測(cè)試
public class ObserverTest {
    public static Clock clock = new Clock();
    public static void main(String[] args) {
        
       clock.addObserver(new Son());
       clock.addObserver(new Father());
       clock.addObserver(new Mother());
       
       clock.turnOn();

    }
}
  • 輸出結(jié)果
鬧鐘響了
爸爸起床上班
媽媽出門買菜
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容