溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java設(shè)計模式之觀察者模式的示例分析

發(fā)布時間:2021-09-15 12:45:55 來源:億速云 閱讀:138 作者:小新 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)Java設(shè)計模式之觀察者模式的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

    介紹

    觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關(guān)系,使得每當(dāng)一個對象狀態(tài)發(fā)生改變時,其相關(guān)依賴對象皆得到通知并被自動更新。觀察者模式是一種對象行為型模式。

    觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。

    觀察者模式包含觀察目標(biāo)和觀察者兩類對象,**一個目標(biāo)可以有任意數(shù)目的與之相依賴的觀察者,**一旦觀察目標(biāo)的狀態(tài)發(fā)生改變,所有的觀察者都將得到通知。

    • 一般是多對一依賴,即一個被觀察者,和多個觀察者

    • 一旦大忽悠更新了公眾號,所有訂閱其公眾號的粉絲都會接收到更新推送

    角色

    Subject(目標(biāo)):目標(biāo)又稱為主題,它是指被觀察的對象。在目標(biāo)中定義了一個觀察者集合,一個觀察目標(biāo)可以接受任意數(shù)量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()。目標(biāo)類可以是接口,也可以是抽象類或具體類。

    ConcreteSubject(具體目標(biāo)):具體目標(biāo)是目標(biāo)類的子類,通常它包含有經(jīng)常發(fā)生改變的數(shù)據(jù),當(dāng)它的狀態(tài)發(fā)生改變時,向它的各個觀察者發(fā)出通知;同時它還實現(xiàn)了在目標(biāo)類中定義的抽象業(yè)務(wù)邏輯方法(如果有的話)。如果無須擴(kuò)展目標(biāo)類,則具體目標(biāo)類可以省略。

    Observer(觀察者):觀察者將對觀察目標(biāo)的改變做出反應(yīng),觀察者一般定義為接口,該接口聲明了更新數(shù)據(jù)的方法update(),因此又稱為抽象觀察者。

    ConcreteObserver(具體觀察者):在具體觀察者中維護(hù)一個指向具體目標(biāo)對象的引用,它存儲具體觀察者的有關(guān)狀態(tài),這些狀態(tài)需要和具體目標(biāo)的狀態(tài)保持一致;它實現(xiàn)了在抽象觀察者Observer中定義的update()方法。通常在實現(xiàn)時,可以調(diào)用具體目標(biāo)類的attach()方法將自己添加到目標(biāo)類的集合中或通過detach()方法將自己從目標(biāo)類的集合中刪除。

    原理類圖

    Java設(shè)計模式之觀察者模式的示例分析

    微信訂閱號的案例

    首先需要一個訂閱者接口(觀察者),該接口有一個 receive 方法,用于接收公眾號推送通知

    //訂閱者---觀察者
    public interface Subscriber
    {
        //接收發(fā)布者發(fā)布消息的方法
        public void receive();
    }

    然后是一個微信客戶端(具體觀察者),實現(xiàn)了 receive 方法

    //處理微信訂閱的業(yè)務(wù)邏輯
    public class WeChatSub implements Subscriber
    {
        //當(dāng)前訂閱者的名字
        private String subName;
        WeChatSub(String subName)
        {
            this.subName=subName;
        }
        @Override
        public void receive(String publisher, String passageName) {
           //接收到推送消息時的具體業(yè)務(wù)邏輯操作
            System.out.println(String.format("用戶[%s] ,  接收到[%s]的訂閱號推送," +
                    "推送文章為:%s ",subName,publisher,passageName));
        }
    }

    發(fā)布者類(目標(biāo),被觀察對象),該類維護(hù)了一個訂閱者列表,實現(xiàn)了訂閱、取消訂閱、通知所有訂閱者等功能

    //發(fā)布者--被觀察的對象Subject
    public class Publisher
    {
        //存放所有需要通知的觀察者
       static private List<Subscriber> subscribers=new ArrayList<>();
       //記錄是否發(fā)布的狀態(tài),默認(rèn)false
        static private  Boolean pubStatus=false;
        protected void subscribe(Subscriber subscriber) {
            this.subscribers.add(subscriber);
        }
        protected void unsubscribe(Subscriber subscriber) {
            if (this.subscribers.contains(subscriber)) {
                this.subscribers.remove(subscriber);
            }
        }
        protected void notifySubscribers(String publisher, String articleName) {
            if (this.pubStatus == false) {
                return;
            }
            for (Subscriber subscriber : this.subscribers) {
                subscriber.receive(publisher, articleName);
            }
            this.clearPubStatus();
        }
        protected void setPubStatus() {
            this.pubStatus = true;
        }
        protected void clearPubStatus() {
            this.pubStatus = false;
        }
    }

    微信公眾號類(具體目標(biāo)),該類提供了 publishArticles 方法,用于發(fā)布推送,當(dāng)文章發(fā)布完畢時調(diào)用父類的通知所有訂閱者方法

    public class WeChatPublisher extends  Publisher
    {
        private String name;
        public WeChatPublisher(String name) {
            this.name = name;
        }
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("\n<%s>微信公眾號 發(fā)布了一篇推送,文章名稱為 <%s>,內(nèi)容為 <%s> ", this.name, articleName, content));
            setPubStatus();
            notifySubscribers(this.name, articleName);
        }
    }

    客戶端測試

    //客戶端
    public class Client
    {
        public static void main(String[] args) {
            //創(chuàng)建一個發(fā)布者
            WeChatPublisher dhy=new WeChatPublisher("大忽悠");
            //創(chuàng)建訂閱者
            Subscriber sub1=new WeChatSub("小朋友");
            Subscriber sub2=new WeChatSub("小忽悠");
            Subscriber sub3=new WeChatSub("大朋友");
            //訂閱大忽悠公眾號
            dhy.subscribe(sub1);
            dhy.subscribe(sub2);
            dhy.subscribe(sub3);
            //大忽悠發(fā)布推送
            dhy.publishArticles("設(shè)計模式","觀察者模式");
        }
    }

    Java設(shè)計模式之觀察者模式的示例分析

    總結(jié)

    優(yōu)點

    • 觀察者模式可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機(jī)制,并抽象了更新接口,使得可以有各種各樣不同的表示層充當(dāng)具體觀察者角色。

    • 觀察者模式在觀察目標(biāo)和觀察者之間建立一個抽象的耦合。觀察目標(biāo)只需要維持一個抽象觀察者的集合,無須了解其具體觀察者。由于觀察目標(biāo)和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。

    • 觀察者模式支持廣播通信,觀察目標(biāo)會向所有已注冊的觀察者對象發(fā)送通知,簡化了一對多系統(tǒng)設(shè)計的難度。

    • 觀察者模式滿足 “開閉原則”的要求,增加新的具體觀察者無須修改原有系統(tǒng)代碼,在具體觀察者與觀察目標(biāo)之間不存在關(guān)聯(lián)關(guān)系的情況下,增加新的觀察目標(biāo)也很方便。

    缺點

    • 如果一個觀察目標(biāo)對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。

    • 如果在觀察者和觀察目標(biāo)之間存在循環(huán)依賴,觀察目標(biāo)會觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。

    • 觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。

    適用場景

    • 一個抽象模型有兩個方面,其中一個方面依賴于另一個方面,將這兩個方面封裝在獨立的對象中使它們可以各自獨立地改變和復(fù)用。

    • 一個對象的改變將導(dǎo)致一個或多個其他對象也發(fā)生改變,而并不知道具體有多少對象將發(fā)生改變,也不知道這些對象是誰。

    • 需要在系統(tǒng)中創(chuàng)建一個觸發(fā)鏈,A對象的行為將影響B(tài)對象,B對象的行為將影響C對象……,可以使用觀察者模式創(chuàng)建一種鏈?zhǔn)接|發(fā)機(jī)制。

    觀察者模式的典型應(yīng)用

    JDK 提供的觀察者接口

    觀察者模式在Java語言中的地位非常重要。在JDK的 java.util 包中,提供了 Observable 類以及 Observer 接口,它們構(gòu)成了JDK對觀察者模式的支持。

    其中的 Observer 接口為觀察者,只有一個 update 方法,當(dāng)觀察目標(biāo)發(fā)生變化時被調(diào)用,其代碼如下:

    public interface Observer {
        void update(Observable o, Object arg);
    }

    Observable 類則為目標(biāo)類,相比我們的示例中的 Publisher 類多了并發(fā)和NPE方面的考慮

    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs = new Vector();
        public Observable() {
        }
        // 用于注冊新的觀察者對象到向量中
        public synchronized void addObserver(Observer var1) {
            if (var1 == null) {
                throw new NullPointerException();
            } else {
                if (!this.obs.contains(var1)) {
                    this.obs.addElement(var1);
                }
            }
        }
        // 用于刪除向量中的某一個觀察者對象
        public synchronized void deleteObserver(Observer var1) {
            this.obs.removeElement(var1);
        }
        public void notifyObservers() {
            this.notifyObservers((Object)null);
        }
        // 通知方法,用于在方法內(nèi)部循環(huán)調(diào)用向量中每一個觀察者的update()方法
        public void notifyObservers(Object var1) {
            Object[] var2;
            synchronized(this) {
                if (!this.changed) {
                    return;
                }
                var2 = this.obs.toArray();
                this.clearChanged();
            }
            for(int var3 = var2.length - 1; var3 >= 0; --var3) {
                ((Observer)var2[var3]).update(this, var1);
            }
        }
        // 用于清空向量,即刪除向量中所有觀察者對象
        public synchronized void deleteObservers() {
            this.obs.removeAllElements();
        }
        // 該方法被調(diào)用后會設(shè)置一個boolean類型的內(nèi)部標(biāo)記變量changed的值為true,表示觀察目標(biāo)對象的狀態(tài)發(fā)生了變化
        protected synchronized void setChanged() {
            this.changed = true;
        }
        // 用于將changed變量的值設(shè)為false,表示對象狀態(tài)不再發(fā)生改變或者已經(jīng)通知了所有的觀察者對象,調(diào)用了它們的update()方法
        protected synchronized void clearChanged() {
            this.changed = false;
        }
        // 返回對象狀態(tài)是否改變
        public synchronized boolean hasChanged() {
            return this.changed;
        }
        // 返回向量中觀察者的數(shù)量
        public synchronized int countObservers() {
            return this.obs.size();
        }
    }

    我們可以使用 Observable 類以及 Observer 接口來重新實現(xiàn)微信公眾號示例。

    增加一個通知類 WechatNotice,用于推送通知的傳遞

    @Data
    @AllArgsConstructor
    public class WechatNotice {
        private String publisher;
        private String articleName;
    }

    然后改寫 WeChatClient WeChatAccounts,分別實現(xiàn)JDK的 Observer 接口和繼承 Observable

    public class WeChatClient implements Observer {
        private String username;
        public WeChatClient(String username) {
            this.username = username;
        }
        @Override
        public void update(Observable o, Object arg) {
            //WeChatAccounts weChatAccounts = (WeChatAccounts) o;
            WechatNotice notice = (WechatNotice) arg;
            System.out.println(String.format("用戶<%s> 接收到 <%s>微信公眾號 的推送,文章標(biāo)題為 <%s>", username, notice.getPublisher(), notice.getArticleName()));
        }
    }
    public class WeChatAccounts extends Observable {
        private String name;
        public WeChatAccounts(String name) {
            this.name = name;
        }
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("\n<%s>微信公眾號 發(fā)布了一篇推送,文章名稱為 <%s>,內(nèi)容為 <%s> ", this.name, articleName, content));
            setChanged();
            notifyObservers(new WechatNotice(this.name, articleName));
        }
    }

    測試,與示例中的測試代碼的區(qū)別在于調(diào)用的方法不同

    public class Test {
        public static void main(String[] args) {
            WeChatAccounts accounts = new WeChatAccounts("大忽悠");
            WeChatClient user1 = new WeChatClient("張三");
            WeChatClient user2 = new WeChatClient("李四");
            WeChatClient user3 = new WeChatClient("王五");
            accounts.addObserver(user1);
            accounts.addObserver(user2);
            accounts.addObserver(user3);
            accounts.publishArticles("設(shè)計模式 | 觀察者模式及典型應(yīng)用", "觀察者模式的內(nèi)容...");
            accounts.deleteObserver(user1);
            accounts.publishArticles("設(shè)計模式 | 單例模式及典型應(yīng)用", "單例模式的內(nèi)容....");
        }
    }

    Guava EventBus 中的觀察者模式

    Guava 中的 EventBus 封裝了友好的 “生產(chǎn)/消費模型”,通過非常簡單的方式,實現(xiàn)了觀察者模式中的監(jiān)聽注冊,事件分發(fā)。

    使用了 Guava EventBus 之后,如果需要訂閱消息,不需要實現(xiàn)任何接口,只需在監(jiān)聽方法上加上 @Subscribe 注解即可,EventBus 提供了 registerunregister 方法用于注冊與取消注冊事件,當(dāng) EventBus 調(diào)用 post 方法時將把事件分發(fā)給注冊的對象

    使用 Guava 重新實現(xiàn)示例

    @Data
    @AllArgsConstructor
    public class WechatNotice {
        private String publisher;
        private String articleName;
    }
    public class WeChatClient  {
        private String username;
        public WeChatClient(String username) {
            this.username = username;
        }
        @Subscribe
        public void listen(WechatNotice notice) {
            System.out.println(String.format("用戶<%s> 接收到 <%s>微信公眾號 的推送,文章標(biāo)題為 <%s>", username, notice.getPublisher(), notice.getArticleName()));
        }
    }
    public class WeChatAccounts {
        private String name;
        private EventBus eventBus;
        public WeChatAccounts(String name) {
            this.name = name;
            this.eventBus = new EventBus();
        }
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("\n<%s>微信公眾號 發(fā)布了一篇推送,文章名稱為 <%s>,內(nèi)容為 <%s> ", this.name, articleName, content));
            //post方法將會將被派發(fā)的消息,傳遞給所有的訂閱者,并調(diào)用訂閱者的監(jiān)聽方法
            this.eventBus.post(new WechatNotice(this.name, articleName));
        }
        public void register(WeChatClient weChatClient) {
            this.eventBus.register(weChatClient);
        }
        public void unregister(WeChatClient weChatClient) {
            this.eventBus.unregister(weChatClient);
        }
    }

    測試

    public class Test {
        public static void main(String[] args) {
            WeChatAccounts accounts = new WeChatAccounts("小旋鋒");
            WeChatClient user1 = new WeChatClient("張三");
            WeChatClient user2 = new WeChatClient("李四");
            WeChatClient user3 = new WeChatClient("王五");
            accounts.register(user1);
            accounts.register(user2);
            accounts.register(user3);
            accounts.publishArticles("設(shè)計模式 | 觀察者模式及典型應(yīng)用", "觀察者模式的內(nèi)容...");
            accounts.unregister(user1);
            accounts.publishArticles("設(shè)計模式 | 單例模式及典型應(yīng)用", "單例模式的內(nèi)容....");
        }
    }

    Spring ApplicationContext 事件機(jī)制中的觀察者模式

    spring的事件機(jī)制是從java的事件機(jī)制拓展而來,ApplicationContext 中事件處理是由 ApplicationEvent 類和 ApplicationListener 接口來提供的。如果一個Bean實現(xiàn)了 ApplicationListener 接口,并且已經(jīng)發(fā)布到容器中去,每次 ApplicationContext 發(fā)布一個 ApplicationEvent 事件,這個Bean就會接到通知

    • ApplicationContext:事件源,其中的 publishEvent()方法用于觸發(fā)容器事件

    • ApplicationEvent:事件本身,自定義事件需要繼承該類,可以用來傳遞數(shù)據(jù)

    • ApplicationListener:事件監(jiān)聽器接口,事件的業(yè)務(wù)邏輯封裝在監(jiān)聽器里面

    @Data
    public class WechatNotice extends ApplicationEvent {
        private String publisher;
        private String articleName;
        public WechatNotice(Object source, String publisher, String articleName) {
            super(source);
            this.publisher = publisher;
            this.articleName = articleName;
        }
    }
    public class WeChatClient implements ApplicationListener {
        private String username;
        public WeChatClient(String username) {
            this.username = username;
        }
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof WechatNotice) {
                WechatNotice notice = (WechatNotice) event;
                System.out.println(String.format("用戶<%s> 接收到 <%s>微信公眾號 的推送,文章標(biāo)題為 <%s>", username, notice.getPublisher(), notice.getArticleName()));
            }
        }
        public void setUsername(String username) {
            this.username = username;
        }
    }
    public class WeChatAccounts implements ApplicationContextAware {
        private ApplicationContext ctx;
        private String name;
        public WeChatAccounts(String name) {
            this.name = name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.ctx = applicationContext;
        }
        public void publishArticles(String articleName, String content) {
            System.out.println(String.format("\n<%s>微信公眾號 發(fā)布了一篇推送,文章名稱為 <%s>,內(nèi)容為 <%s> ", this.name, articleName, content));
            ctx.publishEvent(new WechatNotice(this.name, this.name, articleName));
        }
    }

    在 resources 目錄下創(chuàng)建 spring.xml 文件,填入下面的內(nèi)容

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="WeChatAccounts" class="com.observer.sprintevent.WeChatAccounts" scope="prototype">
            <constructor-arg name="name" value=""></constructor-arg>
        </bean>
        <bean id="WeChatClient1" class="com.observer.sprintevent.WeChatClient">
            <constructor-arg name="username" value="張三"></constructor-arg>
        </bean>
        <bean id="WeChatClient2" class="com.observer.sprintevent.WeChatClient">
            <constructor-arg name="username" value="李四"></constructor-arg>
        </bean>
        <bean id="WeChatClient3" class="com.observer.sprintevent.WeChatClient">
            <constructor-arg name="username" value="王五"></constructor-arg>
        </bean>
    </beans>

    測試

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
            WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts");
            accounts.setName("大忽悠");
            accounts.setApplicationContext(context);
            accounts.publishArticles("設(shè)計模式 | 觀察者模式及典型應(yīng)用", "觀察者模式的內(nèi)容...");
        }
    }

    在此示例中 ApplicationContext 對象的實際類型為 ClassPathXmlApplicationContext,其中的與 publishEvent 方法相關(guān)的主要代碼如下:

    private ApplicationEventMulticaster applicationEventMulticaster;
    public void publishEvent(ApplicationEvent event) {
        this.getApplicationEventMulticaster().multicastEvent(event);
        if (this.parent != null) {
            this.parent.publishEvent(event);
        }
    }
    ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
        return this.applicationEventMulticaster;
    }
    protected void initApplicationEventMulticaster() {
            ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
            if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
                this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
            } else {
                this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
                beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
            }
        }

    其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通過遍歷 ApplicationListener(注冊由 AbstractApplicationEventMulticaster 實現(xiàn)),使用線程池框架 Executor 來并發(fā)執(zhí)行 ApplicationListener 的 onApplicationEvent 方法,與示例本質(zhì)上是一致的

    public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
        private Executor taskExecutor;
        public void multicastEvent(final ApplicationEvent event) {
            Iterator var2 = this.getApplicationListeners(event).iterator();
            while(var2.hasNext()) {
                final ApplicationListener listener = (ApplicationListener)var2.next();
                Executor executor = this.getTaskExecutor();
                if (executor != null) {
                    executor.execute(new Runnable() {
                        public void run() {
                            listener.onApplicationEvent(event);
                        }
                    });
                } else {
                    listener.onApplicationEvent(event);
                }
            }
        }
    }

    關(guān)于“Java設(shè)計模式之觀察者模式的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI