1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        聊一聊Java中的事件監(jiān)聽(tīng)機(jī)制

        共 22046字,需瀏覽 45分鐘

         ·

        2021-05-28 18:29

        汪偉俊 作者

        Java技術(shù)迷 | 出品


        相信大家都學(xué)過(guò)Java中的GUI,不知道你們對(duì)GUI中的事件機(jī)制有沒(méi)有產(chǎn)生過(guò)好奇心,當(dāng)我們點(diǎn)擊按鈕時(shí),就可以觸發(fā)對(duì)應(yīng)的點(diǎn)擊事件,這一過(guò)程究竟是如何實(shí)現(xiàn)的呢?本篇文章我們就來(lái)聊一聊Java中的事件監(jiān)聽(tīng)機(jī)制。

        在了解事件監(jiān)聽(tīng)機(jī)制之前,我們先來(lái)學(xué)習(xí)一個(gè)設(shè)計(jì)模式——觀察者模式,事件監(jiān)聽(tīng)機(jī)制的原理就是它。

        場(chǎng)景設(shè)置

        假設(shè)現(xiàn)在有一個(gè)需求,你正在運(yùn)營(yíng)一個(gè)有關(guān)天氣的接口,要求是可以將天氣信息推送出去,前提是接入了該接口的開(kāi)發(fā)者才能收到天氣信息,該如何實(shí)現(xiàn)呢?

        首先我們來(lái)創(chuàng)建一個(gè)類:

        package com.wwj.spring.guanchazhe;

        /**
         * 顯示天氣信息
         */

        public class PushWeather {

            private int temperature;
            private int humidity;
            private int airPressure;

            public void update(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                show();
            }

            public void show() {
                System.out.print("溫度:" + temperature + "\t");
                System.out.print("濕度:" + humidity + "\t");
                System.out.print("氣壓:" + airPressure + "\t");
                System.out.println();
            }
        }

        該類模擬的是第三方開(kāi)發(fā)者接入我們的數(shù)據(jù)接口,顯示天氣信息,其中成員屬性分別為溫度、濕度和氣壓,并提供update方法用于更新數(shù)據(jù)(該方法是由其它類調(diào)用的)。

        繼續(xù)創(chuàng)建一個(gè)類:

        public class WeatherDataInterface {

            private int temperature;
            private int humidity;
            private int airPressure;
            private PushWeather pushWeather;

            public WeatherDataInterface(PushWeather pushWeather) {
                this.pushWeather = pushWeather;
            }

            public void update() {
                pushWeather.update(temperature, humidity, airPressure);
            }

            public void updateWeatherData(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                update();
            }
        }

        該類就是天氣數(shù)據(jù)接口類,類中包含了第三方開(kāi)發(fā)者PushWeather,當(dāng)我們調(diào)用updateWeatherData更新接口中的天氣信息時(shí),它會(huì)同步調(diào)用第三方開(kāi)發(fā)者的update方法實(shí)現(xiàn)數(shù)據(jù)同步,下面我們就來(lái)試一試:

        public class Main {

            public static void main(String[] args) {
                PushWeather pushWeather = new PushWeather();
                WeatherDataInterface wdi = new WeatherDataInterface(pushWeather);
                wdi.updateWeatherData(102030);
                System.out.println("更新天氣數(shù)據(jù)");
                wdi.updateWeatherData(203040);
            }
        }

        運(yùn)行結(jié)果:

        溫度:10 濕度:20 氣壓:30 
        更新天氣數(shù)據(jù)
        溫度:20 濕度:30 氣壓:40 

        這種實(shí)現(xiàn)方式是有很大弊端的,因?yàn)槿绻钟幸粋€(gè)第三方開(kāi)發(fā)者要接入你的接口,那么修改的代碼將會(huì)非常多,不信來(lái)看看,首先創(chuàng)建第三方開(kāi)發(fā)者:

        public class Baidu {

            private int temperature;
            private int humidity;
            private int airPressure;

            public void update(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                show();
            }

            public void show() {
                System.out.print("百度接入————溫度:" + temperature + "\t");
                System.out.print("百度接入————濕度:" + humidity + "\t");
                System.out.print("百度接入————?dú)鈮?" + airPressure + "\t");
                System.out.println();
            }
        }

        然后需要修改的是我們的天氣數(shù)據(jù)接口:

        public class WeatherDataInterface {

            private int temperature;
            private int humidity;
            private int airPressure;
            private PushWeather pushWeather;
            private Baidu baidu;

            public WeatherDataInterface(PushWeather pushWeather,Baidu baidu) {
                this.pushWeather = pushWeather;
                this.baidu = baidu;
            }

            public void update() {
                pushWeather.update(temperature, humidity, airPressure);
                baidu.update(temperature,humidity,airPressure);
            }

            public void updateWeatherData(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                update();
            }
        }

        首先需要添加百度到成員變量,然后修改構(gòu)造方法, 還需要修改update方法,讓其也能更新百度的數(shù)據(jù),測(cè)試代碼:

        public class Main {

            public static void main(String[] args) {
                PushWeather pushWeather = new PushWeather();
                Baidu baidu = new Baidu();
                WeatherDataInterface wdi = new WeatherDataInterface(pushWeather,baidu);
                wdi.updateWeatherData(102030);
                System.out.println("更新天氣數(shù)據(jù)");
                wdi.updateWeatherData(203040);
            }
        }

        運(yùn)行結(jié)果:

        溫度:10 濕度:20 氣壓:30 
        百度接入————溫度:10 百度接入————濕度:20 百度接入————?dú)鈮?30 
        更新天氣數(shù)據(jù)
        溫度:20 濕度:30 氣壓:40 
        百度接入————溫度:20 百度接入————濕度:30 百度接入————?dú)鈮?40 

        觀察者模式

        觀察者模式,又被稱為發(fā)布——訂閱模式,它定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象,當(dāng)該主題對(duì)象發(fā)生數(shù)據(jù)變化時(shí),會(huì)通知所有的觀察者對(duì)象更新數(shù)據(jù)。很顯然,在剛才的案例中,第三方開(kāi)發(fā)者就是觀察者模式中的觀察者,而天氣數(shù)據(jù)接口就是主題對(duì)象,當(dāng)天氣數(shù)據(jù)接口發(fā)生變化時(shí),就會(huì)通知那些依賴于天氣接口的觀察者去更新自己的數(shù)據(jù),所以剛才的案例是非常適合使用觀察者模式來(lái)進(jìn)行改造的,那怎么實(shí)現(xiàn)呢?

        觀察者模式中有幾個(gè)非常重要的概念:

        1. Subject:抽象主題,它是用于抽象觀察者的,因?yàn)橹黝}對(duì)象需要管理所有依賴于它的觀察者,所以必須對(duì)觀察者抽象,才能實(shí)現(xiàn)統(tǒng)一的管理,提供接口注冊(cè)和注銷觀察者
        2. ConcreteSubject:具體主題,它用于具體實(shí)現(xiàn)主題對(duì)象,它會(huì)將有關(guān)狀態(tài)存入具體的觀察者對(duì)象,在具體主題數(shù)據(jù)發(fā)生變化時(shí),會(huì)給所有已經(jīng)注冊(cè)的觀察者發(fā)送通知
        3. Observer:抽象觀察者,它定義了一個(gè)接口,用于對(duì)觀察者進(jìn)行抽象
        4. ConcreteObserver:具體觀察者,實(shí)現(xiàn)抽象觀察者接口,以便在得到主題對(duì)象的通知時(shí)更新自身數(shù)據(jù)

        現(xiàn)在我們就來(lái)改造剛才的案例,首先創(chuàng)建抽象主題:

        public interface Subject {

            // 注冊(cè)觀察者對(duì)象
            void register(Observer observer);

            // 移除觀察者對(duì)象
            void remove(Observer observer);

            // 通知所有觀察者更新數(shù)據(jù)
            void notify(int temperature, int humidity, int airPressure);
        }

        然后創(chuàng)建抽象觀察者:

        public interface Observer {

            // 更新天氣數(shù)據(jù)
            void update(int temperature, int humidity, int airPressure);
        }

        接著具體實(shí)現(xiàn)主題:

        public class WeatherDataSubject implements Subject {

            // 管理所有觀察者
            private Vector<Observer> vector;

            public WeatherDataSubject() {
                vector = new Vector<>();
            }

            @Override
            public void register(Observer observer) {
                vector.add(observer);
            }

            @Override
            public void remove(Observer observer) {
                vector.remove(observer);
            }

            @Override
            public void notify(int temperature, int humidity, int airPressure) {
                for (Observer observer : vector) {
                    observer.update(temperature, humidity, airPressure);
                }
            }
        }

        最后就是創(chuàng)建具體的觀察者,也就是第三方開(kāi)發(fā)者:

        public class Baidu implements Observer {

            private int temperature;
            private int humidity;
            private int airPressure;

            public void show() {
                System.out.print("百度接入————溫度:" + temperature + "\t");
                System.out.print("百度接入————濕度:" + humidity + "\t");
                System.out.print("百度接入————?dú)鈮?" + airPressure + "\t");
                System.out.println();
            }

            @Override
            public void update(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                show();
            }
        }

        編寫(xiě)測(cè)試代碼:

        public class Main {

            public static void main(String[] args) {
                Baidu baidu = new Baidu();
                WeatherDataSubject subject = new WeatherDataSubject();
                subject.register(baidu);
                subject.notify(102030);
                System.out.println("更新天氣數(shù)據(jù)");
                subject.notify(203040);
            }
        }

        運(yùn)行結(jié)果:

        百度接入————溫度:10 百度接入————濕度:20 百度接入————?dú)鈮?30 
        更新天氣數(shù)據(jù)
        百度接入————溫度:20 百度接入————濕度:30 百度接入————?dú)鈮?40 

        現(xiàn)在若是想接入新的第三方開(kāi)發(fā)者,那就變得非常簡(jiǎn)單了,首先創(chuàng)建新的開(kāi)發(fā)者:

        public class Alibaba implements Observer {
            private int temperature;
            private int humidity;
            private int airPressure;

            public void show() {
                System.out.print("阿里巴巴接入————溫度:" + temperature + "\t");
                System.out.print("阿里巴巴接入————濕度:" + humidity + "\t");
                System.out.print("阿里巴巴接入————?dú)鈮?" + airPressure + "\t");
                System.out.println();
            }

            @Override
            public void update(int temperature, int humidity, int airPressure) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.airPressure = airPressure;
                show();
            }
        }

        然后修改測(cè)試代碼即可:

        public class Main {

            public static void main(String[] args) {
                Baidu baidu = new Baidu();
                Alibaba alibaba = new Alibaba();
                WeatherDataSubject subject = new WeatherDataSubject();
                subject.register(baidu);
                subject.register(alibaba);
                subject.notify(102030);
                System.out.println("更新天氣數(shù)據(jù)");
                subject.notify(203040);
            }
        }

        運(yùn)行結(jié)果:

        百度接入————溫度:10 百度接入————濕度:20 百度接入————?dú)鈮?30 
        阿里巴巴接入————溫度:10 阿里巴巴接入————濕度:20 阿里巴巴接入————?dú)鈮?30 
        更新天氣數(shù)據(jù)
        百度接入————溫度:20 百度接入————濕度:30 百度接入————?dú)鈮?40 
        阿里巴巴接入————溫度:20 阿里巴巴接入————濕度:30 阿里巴巴接入————?dú)鈮?40

        通過(guò)觀察者模式極大地解除了程序間的耦合,雖然主題對(duì)象中仍然依賴了一個(gè)集合類型,但它已經(jīng)被抽象化了,所以耦合度其實(shí)并不算很高,通過(guò)這種方式,我們?cè)诮尤胄碌拈_(kāi)發(fā)者時(shí),只需向主題對(duì)象注冊(cè)即可,若是不想接入了,也可以注銷該開(kāi)發(fā)者。

        事件監(jiān)聽(tīng)機(jī)制

        了解觀察者模式之后,我們進(jìn)入本篇文章的重心,事件監(jiān)聽(tīng)機(jī)制。

        在該模型中,有三個(gè)非常重要的概念:

        1. 事件
        2. 事件源
        3. 事件監(jiān)聽(tīng)器

        其具體流程是:用戶操作(比如點(diǎn)擊)導(dǎo)致事件觸發(fā),前提是事件監(jiān)聽(tīng)器已經(jīng)被注冊(cè)好了,事件觸發(fā)后會(huì)生成事件對(duì)象,此時(shí)事件對(duì)象會(huì)作為參數(shù)傳遞給事件監(jiān)聽(tīng)器,監(jiān)聽(tīng)器調(diào)用對(duì)應(yīng)的方法進(jìn)行處理。

        在這里事件源就是主題對(duì)象,而事件監(jiān)聽(tīng)器就是觀察者,當(dāng)事件源發(fā)生變化時(shí),主題對(duì)象就會(huì)通知所有的觀察者處理數(shù)據(jù),那么接下來(lái)我們就來(lái)實(shí)現(xiàn)一下。首先創(chuàng)建事件接口:

        public interface Event {

            // 事件回調(diào)
            void callback();
        }

        然后創(chuàng)建具體實(shí)現(xiàn):

        public class ValueEvent implements Event {

            // 事件三要素:事件源、事件發(fā)生事件、事件消息
            private Object source;
            private LocalDateTime when;
            private String msg;

            public void setSource(Object source) {
                this.source = source;
            }

            public void setWhen(LocalDateTime when) {
                this.when = when;
            }

            public void setMsg(String msg) {
                this.msg = msg;
            }

            public Object getSource() {
                return source;
            }

            public LocalDateTime getWhen() {
                return when;
            }

            public String getMsg() {
                return msg;
            }

            @Override
            public String toString() {
                return "ValueEvent{" +
                        "source=" + source +
                        ", when=" + when +
                        ", msg='" + msg + '\'' +
                        '}';
            }

            @Override
            public void callback() {
                System.out.println(this);
            }
        }

        創(chuàng)建監(jiān)聽(tīng)器接口:

        public interface EventListener {

            // 觸發(fā)事件
            void triggerEvent(Event event);
        }

        實(shí)現(xiàn)監(jiān)聽(tīng)器:

        public class ValueChangeListener implements EventListener {

            @Override
            public void triggerEvent(Event event) {
                // 調(diào)用事件回調(diào)方法
                event.callback();
            }
        }

        最后編寫(xiě)事件源接口:

        public interface EventSource {

            // 注冊(cè)監(jiān)聽(tīng)器
            void addListener(EventListener listener);

            // 通知所有監(jiān)聽(tīng)器
            void notifyListener();
        }

        實(shí)現(xiàn)事件源接口:

        public class ValueSource implements EventSource {

            // 管理所有監(jiān)聽(tīng)器
            private Vector<EventListener> listeners;

            private String msg;

            public ValueSource() {
                listeners = new Vector<>();
            }

            @Override
            public void addListener(EventListener listener) {
                listeners.add(listener);
            }

            @Override
            public void notifyListener() {
                for (EventListener listener : listeners) {
                    ValueEvent event = new ValueEvent();
                    event.setSource(this);
                    event.setWhen(LocalDateTime.now());
                    event.setMsg("更新數(shù)據(jù):" + msg);
                }
            }

            public String getMsg() {
                return msg;
            }

            public void setMsg(String msg) {
                this.msg = msg;
                notifyListener();
            }
        }

        編寫(xiě)測(cè)試代碼:

        public class Main {

            public static void main(String[] args) {
                ValueSource source = new ValueSource();
                source.addListener(new ValueChangeListener());
                source.setMsg("50");
            }
        }

        運(yùn)行結(jié)果:

        ValueEvent{source=com.wwj.spring.guanchazhe.click.ValueSource@1d81eb93, when=2021-05-22T13:19:26.806, msg='更新數(shù)據(jù):50'}

        我們來(lái)仔細(xì)分析一下這個(gè)過(guò)程,首先我們創(chuàng)建了一個(gè)事件源:

        ValueSource source = new ValueSource();

        它相當(dāng)于觀察者模式中的主題對(duì)象,也就是被觀察者,當(dāng)被觀察者數(shù)據(jù)發(fā)生變化時(shí),通知所有監(jiān)聽(tīng)器進(jìn)行處理,所以我們?yōu)槠渥?cè)了一個(gè)監(jiān)聽(tīng)器:

        source.addListener(new ValueChangeListener());

        此時(shí)我們修改事件源的數(shù)據(jù):

        source.setMsg("50");

        就會(huì)執(zhí)行setMsg方法:

        public void setMsg(String msg) {
            this.msg = msg;
            notifyListener();
        }

        該方法又調(diào)用了notifyListener方法,通知所有監(jiān)聽(tīng)器處理:

        @Override
        public void notifyListener() {
            for (EventListener listener : listeners) {
                ValueEvent event = new ValueEvent();
                event.setSource(this);
                event.setWhen(LocalDateTime.now());
                event.setMsg("更新數(shù)據(jù):" + msg);
                listener.triggerEvent(event);
            }
        }

        在該方法中,首先需要?jiǎng)?chuàng)建事件,并設(shè)置事件源,也就是當(dāng)前對(duì)象,設(shè)置事件發(fā)生時(shí)間和消息,最后調(diào)用監(jiān)聽(tīng)器的事件處理方法:

        @Override
        public void triggerEvent(Event event) {
            // 調(diào)用事件回調(diào)方法
            event.callback();
        }

        該方法又調(diào)用了事件的回調(diào)方法:

        @Override
        public void callback() {
            System.out.println(this);
        }

        事件回調(diào)方法就輸出了當(dāng)前對(duì)象,以上就是整個(gè)事件監(jiān)聽(tīng)機(jī)制的流程。

        總結(jié)

        最后,我們通過(guò)這張圖,再總結(jié)一下事件監(jiān)聽(tīng)的整個(gè)流程:

        1. 首先創(chuàng)建事件源,并為其注冊(cè)事件
        2. 當(dāng)調(diào)用setMsg方法修改事件源中的數(shù)據(jù)時(shí),會(huì)調(diào)用notifyListener方法通知所有監(jiān)聽(tīng)器
        3. 在notifyListener方法中會(huì)遍歷所有的監(jiān)聽(tīng)器,創(chuàng)建事件對(duì)象,并作為參數(shù)傳入監(jiān)聽(tīng)器的事件處理方法(triggerEvent)
        4. 監(jiān)聽(tīng)器的triggerEvent方法會(huì)調(diào)用事件的回調(diào)方法(callback)
        5. 回調(diào)方法用于編寫(xiě)具體的處理邏輯,比如輸出內(nèi)容給用戶反饋
        好了,以上就是本篇文章的全部?jī)?nèi)容了。我覺(jué)得寫(xiě)的還算通俗易懂,希望對(duì)你入門(mén)有幫助吧!

        本文作者:汪偉俊 為Java技術(shù)迷專欄作者 投稿,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載。

        1、Intellij IDEA這樣 配置注釋模板,讓你瞬間高出一個(gè)逼格!
        2、吊炸天的 Docker 圖形化工具 Portainer,必須推薦給你!
        3、最牛逼的 Java 日志框架,性能無(wú)敵,橫掃所有對(duì)手!
        4、把Redis當(dāng)作隊(duì)列來(lái)用,真的合適嗎?
        5、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
        6、全網(wǎng)最全 Java 日志框架適配方案!還有誰(shuí)不會(huì)?
        7、Spring中毒太深,離開(kāi)Spring我居然連最基本的接口都不會(huì)寫(xiě)了

        點(diǎn)分享

        點(diǎn)收藏

        點(diǎn)點(diǎn)贊

        點(diǎn)在看

        瀏覽 85
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            姝姝窝人体色www精品 | 亲摸下的超爽小视频 | 掀开白丝袜jk裙子扒掉内裤 | 日韩精品在线观看一区 | 成人免费毛片在线观看 | 久久久久久九九99精品大 | 欧美黑人╳╳ⅹ╳高潮猛交看 | 国产特级黄色一级片 | 日韩一区二区精品日韩波多野结衣 | 亚洲视频中文字幕在线播放 |