設(shè)計(jì)模式解析五 觀察者模式和橋接模式

一. 前言

第五篇要講的也是一個(gè)行為型模式,觀察者模式,話不多說,直接開始吧。

二. 觀察者模式

不知道大家有沒有向出版社訂閱過雜志的經(jīng)歷,我小時(shí)候定過,隱約記得拿本書叫《青年文摘》。
那么出版社有這么多雜志類型,有娛樂雜志、有經(jīng)濟(jì)雜志、有科技雜志,我們訂閱的時(shí)候選擇其中一種或幾種來訂閱,雜志出版的時(shí)候就會(huì)像所有訂閱了雜志的人郵寄雜志,事實(shí)上這種出版+訂閱的模式就是觀察者模式。
我們來實(shí)現(xiàn)代碼,首先我們定一個(gè)觀察者,也就是我們這些要訂書的訂閱者們的接口:

public interface Observer {
    void post(String book);
}

再定義個(gè)雜志社:

public enum MagazineType {
    // 娛樂雜志
    ENTERTAINMENT,
    // 科技雜志
    SCIENCE
}
public interface PeriodicalOffice {
    /**
     * 訂閱雜志
     */
    void subscribe(Observer observer,MagazineType type);
    /**
     * 取消訂閱
     */
    void unSubscribe(Observer observer,MagazineType type);
    /**
     * 出版雜志,出版時(shí)會(huì)向所有訂閱者郵寄雜志
     */
    void publish(MagazineType type, String book);
}

讓我們開一家星星雜志社好了:

public class StarPeriodicalOffice implements PeriodicalOffice {
    // 存儲(chǔ)所有訂閱者
    private static final Map<MagazineType, List<Observer>> observers = new ConcurrentHashMap<>();
    @Override
    public void subscribe(Observer observer, MagazineType type) {
        List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
        if (!list.contains(observer)) {
            list.add(observer);
            observers.put(type, list);
        }
    }
    @Override
    public void unSubscribe(Observer observer, MagazineType type) {
        List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
        list.remove(observer);
    }
    @Override
    public void publish(MagazineType type, String book) {
        List<Observer> list = observers.getOrDefault(type, new ArrayList<>());
        list.forEach(o -> o.post(book));
    }
}

杰克和蘿絲來訂閱雜志了:

public class Jack implements Observer {
    @Override
    public void post(String book) {
        System.out.println("杰克收到了雜志社郵寄來的書:" + book);
    }
}
public class Rose implements Observer {
    @Override
    public void post(String book) {
        System.out.println("蘿絲收到了雜志社郵寄來的書:" + book);
    }
}

接下來是雜志訂閱和發(fā)布的過程:

    public static void main(String[] args) {
        PeriodicalOffice office = new StarPeriodicalOffice();
        Observer jack = new Jack();
        Observer rose = new Rose();
        office.subscribe(jack, MagazineType.ENTERTAINMENT);
        office.subscribe(jack, MagazineType.SCIENCE);
        office.subscribe(rose, MagazineType.ENTERTAINMENT);

        office.publish(MagazineType.SCIENCE, "科學(xué)故事");
        office.publish(MagazineType.ENTERTAINMENT, "娛樂圈的故事");
    }

執(zhí)行結(jié)果:

杰克收到了雜志社郵寄來的書:科學(xué)故事
杰克收到了雜志社郵寄來的書:娛樂圈的故事
蘿絲收到了雜志社郵寄來的書:娛樂圈的故事

這就是觀察者模式,觀察者模式在spring框架中也有運(yùn)用。
Spring的事件監(jiān)聽機(jī)制就是觀察者模式的實(shí)現(xiàn):

@Configuration
public class AppEvent implements ApplicationListener<ApplicationEvent> {

    private static final LoggerAdapter LOGGER = LoggerFactory.getLogger(AppEvent.class);
    @Autowired
    private Environment environment;
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationReadyEvent) {
            LOGGER.info("應(yīng)用已啟動(dòng), 當(dāng)前運(yùn)行的端口是: {} ", environment.getProperty("server.port"));
            return;
        }
        if (event instanceof ApplicationFailedEvent) {
            LOGGER.info("應(yīng)用啟動(dòng)失敗");
        }
    }
}

這里的監(jiān)聽是對(duì)所有應(yīng)用ApplicationEvent事件監(jiān)聽,當(dāng)然泛型也可以選擇針對(duì)某個(gè)之間進(jìn)行監(jiān)聽,比如只監(jiān)聽ApplicationReadyEvent。

public interface ApplicationEventMulticaster {

    /**
     * Add a listener to be notified of all events.
     * @param listener the listener to add
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * Add a listener bean to be notified of all events.
     * @param listenerBeanName the name of the listener bean to add
     */
    void addApplicationListenerBean(String listenerBeanName);

    /**
     * Remove a listener from the notification list.
     * @param listener the listener to remove
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * Remove a listener bean from the notification list.
     * @param listenerBeanName the name of the listener bean to add
     */
    void removeApplicationListenerBean(String listenerBeanName);

    /**
     * Remove all listeners registered with this multicaster.
     * <p>After a remove call, the multicaster will perform no action
     * on event notification until new listeners are being registered.
     */
    void removeAllListeners();

    /**
     * Multicast the given application event to appropriate listeners.
     * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
     * if possible as it provides a better support for generics-based events.
     * @param event the event to multicast
     */
    void multicastEvent(ApplicationEvent event);

    /**
     * Multicast the given application event to appropriate listeners.
     * <p>If the {@code eventType} is {@code null}, a default type is built
     * based on the {@code event} instance.
     * @param event the event to multicast
     * @param eventType the type of event (can be null)
     * @since 4.2
     */
    void multicastEvent(ApplicationEvent event, ResolvableType eventType);

}

ApplicationEventMulticaster類就是所有的觀察者管理類,所有監(jiān)聽器都會(huì)通過這個(gè)接口來進(jìn)行注冊(cè),遇到應(yīng)用的事件發(fā)生時(shí)也通過該接口來進(jìn)行通知。至于是怎么注冊(cè)進(jìn)來的,就是在spring 容器加載bean時(shí)候判斷我們的bean是否是ApplicationListener的實(shí)現(xiàn)類,如果是就幫我們注冊(cè)進(jìn)來,有興趣的可以去看看源碼。

實(shí)際上Spring的源碼可以說是集設(shè)計(jì)模式大成者,我了解的也是百不足一,其中很多源碼都值得我們?nèi)パ芯繉W(xué)習(xí),不能因?yàn)樗鼮槲覀兲峁┝撕啽愕氖褂梅绞?,我們就不去了解學(xué)習(xí)了。

定義

觀察者模式定義了對(duì)象之間的一對(duì)多依賴,這樣一來,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴者都會(huì)收到通知并自動(dòng)更新。

三. 橋接模式

橋接模式是一個(gè)很難理解的模式,夜也深了,這個(gè)模式會(huì)用例子來解釋,但就不寫代碼了。

小時(shí)候大家一定學(xué)過畫畫,我記得小時(shí)候有一種一套的畫筆,里面會(huì)包含一二十種畫筆,每種顏色不同,小時(shí)候沒覺得什么,但是從程序猿的角度再來思考一下這個(gè)東西。

  • 畫筆可以有很多種類 軟毛、硬毛
  • 畫筆還會(huì)有多種顏色 赤橙黃綠青藍(lán)紫

現(xiàn)在讓我們從程序的角度抽象一下畫家的畫筆:畫筆的材質(zhì)、畫筆顏色
有這兩種可變特性,設(shè)計(jì)接口的話,是不是接口就應(yīng)該有獲取這兩個(gè)屬性的方法,接下來,根據(jù)這兩種特性進(jìn)行組合,然后編寫實(shí)現(xiàn)類的話,總共有14支筆,要寫14個(gè)類,what??如果再加一種毛的材料,我是不是還得再加7個(gè)類,這絕對(duì)不可能。
那么怎么辦?我們是不是可以把這兩種抽象進(jìn)行分離呢?

  • 定義一個(gè)畫筆毛刷種類的接口
  • 定義一個(gè)顏色接口

然后再使用畫筆的時(shí)候在對(duì)這兩種屬性進(jìn)行畫筆的組合,是不是我們就可以做到只有9個(gè)類就行了,而就算任意增加畫筆材質(zhì),或者增加顏色,也只增加一個(gè)類。
所以現(xiàn)實(shí)中我們知道那些畫畫的人總是有畫筆,還有額外的顏料。
而這種把抽象進(jìn)行分離解耦的方式就是橋接模式。

定義

橋接模式即將抽象部分與它的實(shí)現(xiàn)部分分離開來,使他們都可以獨(dú)立變化。

這句話以前一直很不容易理解,現(xiàn)在可以試著理解一下了,說回上面的畫筆,其中畫筆材質(zhì),還有畫筆顏色都是畫筆的抽象,通常來講會(huì)將這兩個(gè)抽象定義到一個(gè)接口里,但由于每一種抽象有可能會(huì)分開變化,所以我們將其中的部分抽象,比如顏色,分離出去,然后通過類的組合關(guān)聯(lián)的方式讓畫筆具有顏色的屬性,這就是抽象部分和實(shí)現(xiàn)部分進(jìn)行分離開來的意思。

套用《大話設(shè)計(jì)模式》里面的就是實(shí)現(xiàn)系統(tǒng)可能有多個(gè)角度分類,每一種角度都可能變化,那么把這種多角度分類給分離出來讓他們獨(dú)立變化,減少他們之間耦合。

橋接模式中的所謂脫耦,就是指在一個(gè)軟件系統(tǒng)的抽象化和實(shí)現(xiàn)化之間使用關(guān)聯(lián)關(guān)系(組合或者聚合關(guān)系)而不是繼承關(guān)系,從而使兩者可以相對(duì)獨(dú)立地變化,這就是橋接模式的用意。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 1.初識(shí)橋接模式 將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。 Abstraction:抽象部分的接口。...
    王偵閱讀 1,017評(píng)論 0 7
  • 【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★☆☆】直接出處:橋接模式梳理和學(xué)習(xí):https://github.com/...
    BruceOuyang閱讀 1,066評(píng)論 0 2
  • 在正式介紹橋接模式之前,我先跟大家談?wù)剝煞N常見文具的區(qū)別,它們是毛筆和蠟筆。假如我們需要大中小3種型號(hào)的畫筆,能夠...
    justCode_閱讀 1,872評(píng)論 0 7
  • Iterator模式 (迭代器) 一個(gè)一個(gè)遍歷 一個(gè)集合類可以遵守 Iterator 協(xié)議,并實(shí)現(xiàn)一個(gè) Itera...
    SSBun閱讀 1,995評(píng)論 0 15
  • 今天說的是一部沉重的電影--《盲山》。 白雪梅是一個(gè)大學(xué)生,被騙到盲山賣給黃德貴做媳婦。整部劇從黃德貴一家及村里人...
    趙小妞_b042閱讀 411評(píng)論 0 0

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