最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java設(shè)計(jì)模式之觀察者模式

 更新時(shí)間:2022年10月11日 09:03:31   作者:tianClassmate  
本文詳細(xì)講解了Java設(shè)計(jì)模式之觀察者模式,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

觀察者模式是極其重要的一個(gè)設(shè)計(jì)模式,也是我?guī)啄觊_發(fā)過程中使用最多的設(shè)計(jì)模式,本文首先概述觀察者模式的基本概念和Demo實(shí)現(xiàn),接著是觀察者模式在Java和Spring中的應(yīng)用,最后是對(duì)觀察者模式的應(yīng)用場(chǎng)景和優(yōu)缺點(diǎn)進(jìn)行總結(jié)。

一、概念理解

觀察者模式:定義對(duì)象之間的一種一對(duì)多的依賴關(guān)系,使得每當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生變化時(shí),其相關(guān)的依賴對(duì)象都可以得到通知并被自動(dòng)更新。主要用于多個(gè)不同的對(duì)象對(duì)一個(gè)對(duì)象的某個(gè)方法會(huì)做出不同的反應(yīng)!

概念啥意思呢?也就是說,如果使用觀察者模式在A的業(yè)務(wù)邏輯中調(diào)用B的業(yè)務(wù)邏輯,即使B的業(yè)務(wù)邏輯報(bào)錯(cuò)了,仍然不影響A的執(zhí)行。

比如,在我最近公司開發(fā)商城系統(tǒng)的過程中,提交訂單成功以后要?jiǎng)h除購(gòu)物車中的信息,如果我先寫訂單提交邏輯,接著寫刪除購(gòu)物車邏輯,這樣當(dāng)然沒有什么問題,但是這樣程序的健壯性太差了。

應(yīng)該將該業(yè)務(wù)分成兩步,一是處理訂單成功處理邏輯,二是刪除購(gòu)物車中的信息。即使刪除購(gòu)物車報(bào)錯(cuò)了,提交訂單邏輯仍然不影響。

那應(yīng)該怎么做才能讓他們互不影響呢?需要在購(gòu)物車對(duì)象中要有一個(gè)方法用于刪除購(gòu)物車,還要有一個(gè)對(duì)象A用于注入(add)購(gòu)物車對(duì)象和通知(notify)購(gòu)物車執(zhí)行它的方法。

在執(zhí)行時(shí)先調(diào)用對(duì)象A的add方法將購(gòu)物車對(duì)象添加到對(duì)象A中,在訂單提交成功以后,調(diào)用對(duì)象A的通知notify購(gòu)物車方法執(zhí)行清除購(gòu)物車邏輯。

在觀察者模式中,購(gòu)物車就稱為觀察者,對(duì)象A就稱為目標(biāo)對(duì)象。在面向接口編程原則下,觀察者模式應(yīng)該包括四個(gè)角色:

1、目標(biāo)接口(subject) :它是一個(gè)抽象類,也是所有目標(biāo)對(duì)象的父類。它用一個(gè)列表記錄當(dāng)前目標(biāo)對(duì)象有哪些觀察者對(duì)象,并提供增加、刪除觀察者對(duì)象和通知觀察者對(duì)象的方法聲明。

2、具體目標(biāo)類:可以有多個(gè)不同的具體目標(biāo)類,它們同時(shí)繼承Subject類。一個(gè)目標(biāo)對(duì)象就是某個(gè)具體目標(biāo)類的對(duì)象,一個(gè)具體目標(biāo)類負(fù)責(zé)定義它自身的事務(wù)邏輯,并在狀態(tài)改變時(shí)通知它的所有觀察者對(duì)象。

3、觀察者接口(Listener) 它也是一個(gè)抽象類,是所有觀察者對(duì)象的父類;它為所有的觀察者對(duì)象都定義了一個(gè)名為update(notify)的方法。當(dāng)目標(biāo)對(duì)象的狀態(tài)改變時(shí),它就是通過調(diào)用它的所有觀察者對(duì)象的update(notify)方法來通知它們的。

4、具體觀察者類,可以有多個(gè)不同的具體觀察者類,它們同時(shí)繼承Listener類。一個(gè)觀察者對(duì)象就是某個(gè)具體觀察者類的對(duì)象。每個(gè)具體觀察者類都要重定義Listener類中定義的update(notify)方法,在該方法中實(shí)現(xiàn)它自己的任務(wù)邏輯,當(dāng)它被通知的時(shí)候(目標(biāo)對(duì)象調(diào)用它的update(notify)方法)就執(zhí)行自己特有的任務(wù)。在我們的例子中是購(gòu)物車觀察者,當(dāng)然還能有別的,如日志觀察者。

我們基于四個(gè)角色實(shí)現(xiàn)demo。

二、案例實(shí)現(xiàn)

目標(biāo)接口:包括注冊(cè)、移除、通知監(jiān)聽者的方法聲明。

/**
 * 這是被觀察的對(duì)象
 * 目標(biāo)類
 * @author tcy
 * @Date 17-09-2022
 */
public interface SubjectAbstract<T> {
    //注冊(cè)監(jiān)聽者
    public void registerListener(T t);
    //移除監(jiān)聽者
    public void removeListener(T t);
    //通知監(jiān)聽者
    public void notifyListener();
}

目標(biāo)接口實(shí)現(xiàn):里面需要一個(gè)listenerList數(shù)組存儲(chǔ)所有的觀察者,需要定義add和remove觀察者的方法,需要給出notify方法通知所有的觀察者對(duì)象。

/**
 * 
 * 具體目標(biāo)類
 * @author tcy
 * @Date 17-09-2022
 */
public class SubjectImpl implements SubjectAbstract<ListenerAbstract> {

    //監(jiān)聽者的注冊(cè)列表
    private List<ListenerAbstract> listenerList = new ArrayList<>();

    @Override
    public void registerListener(ListenerAbstract myListener) {
        listenerList.add(myListener);
    }

    @Override
    public void removeListener(ListenerAbstract myListener) {
        listenerList.remove(myListener);
    }

    @Override
    public void notifyListener() {
        for (ListenerAbstract myListener : listenerList) {
            System.out.println("收到推送事件,開始調(diào)用異步邏輯...");
            myListener.onEvent();
        }
    }
}

觀察者接口:聲明響應(yīng)方法

/**
 * 
 * 觀察者-接口
 * @author tcy
 * @Date 17-09-2022
 */
public interface ListenerAbstract {
    void onEvent();
}

觀察者接口:實(shí)現(xiàn)響應(yīng)方法,處理清除購(gòu)物車的邏輯。

/**
 * 具體觀察者類 購(gòu)物車
 * @author tcy
 * @Date 17-09-2022
 */
public class ListenerMyShopCart implements ListenerAbstract {
    @Override
    public void onEvent() {

            //...省略購(gòu)物車處理邏輯
            System.out.println("刪除購(gòu)物車中的信息...");

    }
}

我們使用Client模擬提交訂單操作。

/**
 * 先使用具體目標(biāo)對(duì)象的registerListener方法添加具體觀察者對(duì)象,
 * 然后調(diào)用其notify方法通知觀察者
 * @author tcy
 * @Date 17-09-2022
 */
public class Client {
    public static void main(String[] args) {

        System.out.println("訂單成功處理邏輯...");
        //創(chuàng)建目標(biāo)對(duì)象
        SubjectImpl subject=new SubjectImpl();

        //具體觀察者注冊(cè)入 目標(biāo)對(duì)象
        ListenerMyShopCart shopCart=new ListenerMyShopCart();
        //向觀察者中注冊(cè)listener
        subject.registerListener(shopCart);

        //發(fā)布事件,通知觀察者
        subject.notifyListener();

    }
}

這樣就實(shí)現(xiàn)了訂單的處理邏輯和購(gòu)物車的邏輯解耦,即使購(gòu)物車邏輯報(bào)錯(cuò)也不會(huì)影響訂單處理邏輯。

既然觀察者模式是很常用的模式,而且抽象觀察者和抽象目標(biāo)類方法聲明都是固定的,作為高級(jí)語(yǔ)言Java,Java設(shè)計(jì)者干脆內(nèi)置兩個(gè)接口,開發(fā)者直接實(shí)現(xiàn)接口就能使用觀察者模式。

三、Java中的觀察者模式

在 Java 中,通過 java.util.Observable 類和 java.util.Observer 接口定義觀察者模式,只要實(shí)現(xiàn)它們的子類就可以編寫觀察者模式實(shí)例。

Observable 類是抽象目標(biāo)類,它有一個(gè) Vector 向量,用于保存所有要通知的觀察者對(duì)象,下面來介紹它最重要的 3 個(gè)方法。

  • void addObserver(Observer o) 方法:用于將新的觀察者對(duì)象添加到向量中。
  • void notifyObservers(Object arg) 方法:調(diào)用向量中的所有觀察者對(duì)象的 update() 方法,通知它們數(shù)據(jù)發(fā)生改變。通常越晚加入向量的觀察者越先得到通知。
  • void setChange() 方法:用來設(shè)置一個(gè) boolean 類型的內(nèi)部標(biāo)志位,注明目標(biāo)對(duì)象發(fā)生了變化。當(dāng)它為真時(shí),notifyObservers() 才會(huì)通知觀察者。

Observer 接口是抽象觀察者,它監(jiān)視目標(biāo)對(duì)象的變化,當(dāng)目標(biāo)對(duì)象發(fā)生變化時(shí),觀察者得到通知,并調(diào)用 void update(Observable o,Object arg) 方法,進(jìn)行相應(yīng)的工作。

我們基于Java的兩個(gè)接口,改造我們的案例。

具體目標(biāo)類:

/**
 * 具體目標(biāo)類 
 * @author tcy
 * @Date 19-09-2022
 */
public class SubjectObservable extends Observable {

    public void notifyListener() {
        super.setChanged();
        System.out.println("收到推送的消息...");
        super.notifyObservers();    //通知觀察者購(gòu)物車事件
    }

}

具體觀察者類:

/**
 * 觀察者實(shí)現(xiàn)類
 * @author tcy
 * @Date 19-09-2022
 */
public class ShopCartObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("清除購(gòu)物車...");

    }
}

依舊是Client模擬訂單處理邏輯。

/**
 * @author tcy
 * @Date 19-09-2022
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("訂單提交成功...");
        SubjectObservable observable = new SubjectObservable();

        Observer shopCartObserver = new ShopCartObserver(); //購(gòu)物車

        observable.addObserver(shopCartObserver);
        observable.notifyListener();


    }

}

這樣也能實(shí)現(xiàn)觀察者邏輯,但Java中的觀察者模式有一定的局限性。

Observable是個(gè)類,而不是一個(gè)接口,沒有實(shí)現(xiàn)Serializable,所以,不能序列化和它的子類,而且他是線程不安全的,無法保證觀察者的執(zhí)行順序。在JDK9之后已經(jīng)啟用了。

寫Java的恐怕沒有不用Spring的了,作為優(yōu)秀的開源框架,Spring中也有觀察者模式的大量應(yīng)用,而且Spring是在java的基礎(chǔ)之上改造的,很好的規(guī)避了Java觀察者模式的不足之處。

四、Spring如何使用觀察者模式

在第一章節(jié)典型的觀察者模式中包含四個(gè)角色:目標(biāo)類、目標(biāo)類實(shí)現(xiàn)、觀察者、觀察者實(shí)現(xiàn)類。而在Spring下的觀察者模式略有不同,Spring對(duì)其做了部分改造。

事件:

Spring中定義最頂層的事件ApplicationEvent,這個(gè)接口最終還是繼承了EventObject接口。

只是在基礎(chǔ)之上增加了構(gòu)造和獲取當(dāng)前時(shí)間戳的方法,Spring所有的事件都要實(shí)現(xiàn)這個(gè)接口,比如Spring中內(nèi)置的ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent...看名字大概就知道這些事件用于哪些地方,分別是容器刷新后、開始時(shí)、停止時(shí)...

目標(biāo)類接口:

Spirng中的ApplicationEventMulticaster接口就是實(shí)例中目標(biāo)類,我們可以對(duì)比我們的目標(biāo)接口和ApplicationEventMulticaster接口,長(zhǎng)的非常像。

觀察者接口:

觀察者ApplicationListener用于監(jiān)聽事件,只有一個(gè)方法onApplicationEvent事件發(fā)生后該事件執(zhí)行。與我們樣例中的抽象觀察者并無太大的不同。

目標(biāo)類實(shí)現(xiàn):

在我們案例中目標(biāo)類的職責(zé)直接在一個(gè)類中實(shí)現(xiàn),注冊(cè)監(jiān)聽器、廣播事件(調(diào)用監(jiān)聽器方法)。

在Spring中兩個(gè)實(shí)現(xiàn)類分別拆分開來,Spring啟動(dòng)過程中會(huì)調(diào)用registerListeners()方法,看名字我們大概就已經(jīng)知道是注冊(cè)所有的監(jiān)聽器,該方法完成原目標(biāo)類的注冊(cè)監(jiān)聽器職責(zé)。

在Spring中事件源ApplicationContext用于廣播事件,用戶不必再顯示的調(diào)用監(jiān)聽器的方法,交給Spring調(diào)用,該方法完成原目標(biāo)類的廣播事件職責(zé)。

我們基于Spring的觀察者模式繼續(xù)改造我們的案例。

購(gòu)物車事件:

/**
 * 購(gòu)物車事件
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class EventShopCart extends ApplicationEvent {

    private String orderId;

    public EventShopCart(Object source, String orderId) {
        super(source);
        this.orderId=orderId;
    }


    public EventShopCart() {
        super(1);
    }
}

發(fā)布者(模擬Spring調(diào)用監(jiān)聽器的方法,實(shí)際開發(fā)不需要寫):

/**
 * 發(fā)布者
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class MyPublisher implements ApplicationContextAware {
    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 發(fā)布事件
     * 監(jiān)聽該事件的監(jiān)聽者都可以獲取消息
     *
     * @param myEvent
     */
    public void workEvent(EventShopCart myEvent) {
        //該方法會(huì)調(diào)用監(jiān)聽器實(shí)現(xiàn)的方法
        applicationContext.publishEvent(myEvent);
    }
}

監(jiān)聽者:

/**
 * 監(jiān)聽者
 * @author tcy
 * @Date 19-09-2022
 */
@Component
public class ListenerShopCart implements ApplicationListener<EventShopCart> {
    @Override
    public void onApplicationEvent(EventShopCart myEvent) {
        System.out.println("清除購(gòu)物車成功...");
    }
}

Client模擬調(diào)用:

/**
 * @author tcy
 * @Date 19-09-2022
 */
public class Client {

    public static void main(String[] args) {
        ApplicationContext ac =new AnnotationConfigApplicationContext("cn.sky1998.behavior.observer.spring");

        System.out.println("訂單提交成功...");
        MyPublisher bean = ac.getBean(MyPublisher.class);
        EventShopCart myEvent = ac.getBean(EventShopCart.class);

        bean.workEvent(myEvent);
    }
}

通過Spring實(shí)現(xiàn)觀察者模式比我們手動(dòng)寫簡(jiǎn)單的多。

使用Spring實(shí)現(xiàn)觀察者模式時(shí),觀察者接口、目標(biāo)接口、目標(biāo)實(shí)現(xiàn),我們都不需要管,只負(fù)責(zé)繼承ApplicationEvent類定義我們自己的事件,并實(shí)現(xiàn)ApplicationListener<自定義事件>接口實(shí)現(xiàn)我們的觀察者,并在對(duì)應(yīng)的業(yè)務(wù)中調(diào)用applicationContext.publishEvent(new ShopCartEvent(cmOrderItemList)),即實(shí)現(xiàn)了觀察者模式。

讀者可以拉取完整代碼本地學(xué)習(xí),實(shí)現(xiàn)代碼均測(cè)試通過上傳到碼云,本地源碼下載。

五、總結(jié)

Spring使用觀察者模式我在很久之前就使用過,但是并不清楚為什么要這樣寫,學(xué)了觀察者模式以后,寫起來變得通透多了。

雖然觀察者模式的概念是:一對(duì)多的依賴關(guān)系,但不一定觀察者有多個(gè)才能使用,我們的例子都是使用的一個(gè)觀察者。

它很好的降低了目標(biāo)與觀察者之間的耦合關(guān)系,目標(biāo)與觀察者建立一套觸發(fā)機(jī)制,也讓他成為了最常見的設(shè)計(jì)模式。

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接

相關(guān)文章

  • springboot整合mybatis實(shí)現(xiàn)簡(jiǎn)單的一對(duì)多級(jí)聯(lián)查詢功能

    springboot整合mybatis實(shí)現(xiàn)簡(jiǎn)單的一對(duì)多級(jí)聯(lián)查詢功能

    這篇文章主要介紹了springboot整合mybatis實(shí)現(xiàn)簡(jiǎn)單的一對(duì)多級(jí)聯(lián)查詢功能,分步驟通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • Spring事務(wù)傳播行為問題解決

    Spring事務(wù)傳播行為問題解決

    這篇文章主要介紹了Spring事務(wù)傳播行為問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • java多線程入門知識(shí)及示例程序

    java多線程入門知識(shí)及示例程序

    java多線程入門知識(shí)及示例程序,大家參考使用吧
    2013-12-12
  • spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析

    spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析

    這篇文章主要介紹了spring session同域下單點(diǎn)登錄實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • 一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法

    一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法

    線程池(thread pool)是一種線程使用模式。線程過多或者頻繁創(chuàng)建和銷毀線程會(huì)帶來調(diào)度開銷,進(jìn)而影響緩存局部性和整體性能。這篇文章主要介紹了一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法,需要的朋友可以參考下
    2019-07-07
  • MybatisPlus分頁(yè)失效不起作用的解決

    MybatisPlus分頁(yè)失效不起作用的解決

    在使用MybatisPlus的selectPage時(shí)發(fā)現(xiàn)分頁(yè)不起作用,每次返回的都是全部的數(shù)據(jù),本文就來介紹一下MybatisPlus分頁(yè)失效不起作用的解決,感興趣的可以了解一下
    2024-03-03
  • springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn)

    springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn)

    這篇文章主要介紹了springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Java數(shù)組的特性_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java數(shù)組的特性_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    數(shù)組是基本上所有語(yǔ)言都會(huì)有的一種數(shù)據(jù)類型,它表示一組相同類型的數(shù)據(jù)的集合,具有固定的長(zhǎng)度,并且在內(nèi)存中占據(jù)連續(xù)的空間。在C,C++等語(yǔ)言中,數(shù)組的定義簡(jiǎn)潔清晰,而在Java中確有一些會(huì)讓人迷惑的特性。本文就嘗試分析這些特性
    2017-04-04
  • Mybatis-plus apply函數(shù)使用場(chǎng)景分析

    Mybatis-plus apply函數(shù)使用場(chǎng)景分析

    Mybatis-plus 里面的 apply方法 是用于拼接自定義的條件判斷,自定義時(shí)間查詢,根據(jù)傳進(jìn)來的開始日期,查詢所有該日期是數(shù)據(jù),但是數(shù)據(jù)庫(kù)中保存是時(shí)間,所以需要使用apply查詢方式并格式化,這篇文章給大家介紹Mybatis-plus apply函數(shù)使用,感興趣的朋友一起看看吧
    2024-02-02
  • 使用maven編譯Java項(xiàng)目實(shí)例

    使用maven編譯Java項(xiàng)目實(shí)例

    這篇文章主要介紹了使用maven編譯Java項(xiàng)目實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下
    2019-06-06

最新評(píng)論

塔城市| 海晏县| 策勒县| 蒲城县| 泰顺县| 石首市| 纳雍县| 志丹县| 玛曲县| 大竹县| 贵阳市| 盐津县| 上饶市| 万载县| 灵石县| 科技| 绍兴县| 喀什市| 莱西市| 宿州市| 阜南县| 温宿县| 德庆县| 婺源县| 皮山县| 靖宇县| 株洲市| 滁州市| 邯郸市| 平塘县| 渑池县| 曲沃县| 桃源县| 府谷县| 台州市| 巴里| 恭城| 大埔县| 平昌县| 册亨县| 巍山|