設(shè)計(jì)模式-觀察者模式和事件機(jī)制

概念

觀察者模式

百科這樣解釋:一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過呼叫各觀察者所提供的方法(updata)來實(shí)現(xiàn)。具有典型的一對(duì)多的關(guān)系。使得每當(dāng)一個(gè)對(duì)象改變狀態(tài),則所有依賴于它的對(duì)象都會(huì)得到通知進(jìn)行更新。觀察者模式的目標(biāo)對(duì)象中保留了觀察者的集合,當(dāng)被觀察者對(duì)象的狀態(tài)發(fā)生變化時(shí),會(huì)遍歷該集合,然后通知(updata)觀察者對(duì)象得到更新。強(qiáng)調(diào)對(duì)象行為 具有觀察者和目標(biāo)的解耦,動(dòng)態(tài)聯(lián)動(dòng)(動(dòng)態(tài)注冊(cè)控制動(dòng)作);廣播通信(面向所有的觀察者)的優(yōu)點(diǎn)
image.png

發(fā)布/訂閱模式(比生產(chǎn)消費(fèi)更抽象)參考

基于典型觀察者模式實(shí)現(xiàn)的生產(chǎn)者和消費(fèi)者弊端:觀察者模式中觀察者需要直接訂閱目標(biāo)事件(直接引用目標(biāo)狀態(tài)),改變后直接接收響應(yīng),由于函數(shù)調(diào)用是同步的(阻塞),在消費(fèi)者的方法沒有返回之前,生產(chǎn)者只好一直等在那邊,阻塞進(jìn)程。 生產(chǎn)者和消費(fèi)者可以是兩個(gè)獨(dú)立的并發(fā)主體。生產(chǎn)者把制造出來的數(shù)據(jù)往緩沖區(qū)一丟,就可以再去生產(chǎn)下一個(gè)數(shù)據(jù),并不用關(guān)心消費(fèi)者狀態(tài); 發(fā)布訂閱模式就類似的多了一個(gè)發(fā)布通道(調(diào)度中心),一方面從生產(chǎn)者接收事件一方面向訂閱者發(fā)布事件。
image.png
1. 觀察者模式的模板

設(shè)計(jì)階段
image.png
//目標(biāo)對(duì)象,它持有觀察者抽象的集合
public class Subject {

    //定義抽象目標(biāo)的狀態(tài)
    private String subjectState;
   //用來保存注冊(cè)的觀察者集合
    private List<Observer_> observerList = new ArrayList<Observer_>();


    //當(dāng)狀態(tài)改變時(shí)去通知
    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        this.notifyObservers(); //通知
    }

    //添加觀察者
    public void attach(Observer_ observer){
        observerList.add(observer);
    }
    //刪除觀察者
    public void detach(Observer_ observer){
        observerList.remove(observer);
    }

    //通知所有的觀察者
    public void notifyObservers(){
        for (Observer_ observer: observerList){
            observer.updata(this);
        }
}

}

//觀察者
public class Observer_ {

    //定義抽象的狀態(tài)
    private String observerState;

    //獲取目標(biāo)類的狀態(tài)同步到觀察者狀態(tài)中
    public void updata(Subject subject){
        observerState = subject.getSubjectState();
        System.out.println("通知:" + observerState);
    }
}

public class Client {

    @Test
    public void testObserver(){
        //1 創(chuàng)建目標(biāo)
        Subject subject = new Subject();
        //2 創(chuàng)建觀察者
        Observer_ observer1 = new Observer_();
        observer1.setObserverState("observer1");
        //注冊(cè)
        subject.attach(observer1);
        //通知
        subject.setSubjectState("這是觀察者模式");
    }

}

image.png

以上就是典型的觀察者模式的模板

2. 區(qū)別對(duì)待觀察者場(chǎng)景的問題

需求:最近經(jīng)常加班,身為項(xiàng)目經(jīng)理的你決定給大家買宵夜,張三只要雞翅,李四想要雞翅或者漢堡。這里宵夜種類就是目標(biāo),張三李四就是觀察者,如果通過以上代碼,是所有注冊(cè)都會(huì)被通知,這顯然是不合理的。代碼修改如下:

觀察者的接口
//觀察者接口定義一個(gè)更新接口的方法,目標(biāo)發(fā)生改變時(shí)通知觀察者進(jìn)行調(diào)用
public interface Observer {

    //更新的接口
    void update(SupperObject objecr);
    //設(shè)置和取得觀察者名稱
    void setObserverName(String observerName);

    String getObserverName();
}
抽象的目標(biāo)類
public abstract class SupperObject {

    //持有的觀察者集合
    public List<Observer> observers = new ArrayList<Observer>();

    //添加 刪除 觀察者
    public void attach(Observer observer){
        observers.add(observer);
    }
    public void detach(Observer observer){
        observers.remove(observer);
    }
    //具體的提醒在子類邏輯中完成
    protected abstract void notifyObservers();
    
}
 //具體的目標(biāo)實(shí)現(xiàn)類
public class ConcreteSupperSubject extends SupperObject {

    //定義宵夜的變量,雞翅,漢堡, 就是根據(jù)這個(gè)變量進(jìn)項(xiàng)更新(目標(biāo)狀態(tài))
     private String supperContent;


    @Override
    protected void notifyObservers() {
        //循環(huán)所有注冊(cè)的觀察者
        for (Observer observer:observers){
            //定義業(yè)務(wù)邏輯
            //1 .如果雞翅就通知張三和李四,2.如果漢堡就只通知李四,3.其他都不通知
            if ("雞翅".equals(this.getSupperContent())){
                if ("張三".equals(observer.getObserverName())){
                    observer.update(this);
                }
                if ("李四".equals(observer.getObserverName())){
                    observer.update(this);
                }
            }
            if ("漢堡".equals(this.supperContent)){
                if ("李四".equals(observer.getObserverName())){
                    observer.update(this);
                }
            }

        }
    }

     public String getSupperContent() {
         return supperContent;
     }

     public void setSupperContent(String supperContent) {
         this.supperContent = supperContent;
         this.notifyObservers(); //通知 區(qū)別對(duì)待觀察者
     }
 }
//觀察者的具體實(shí)現(xiàn)
public class ConcreteObserver implements Observer{

    //定義觀察者名稱 ,宵夜情況,提醒內(nèi)容
    private String observerName;
    private String supperContent;   //目標(biāo)處獲取的狀態(tài)
    private String remindThing;


    @Override
    public void update(SupperObject object) {
        //獲取目標(biāo)的狀態(tài)
        supperContent =  ((ConcreteSupperSubject)object).getSupperContent();
        System.out.println(observerName+"收到了"+supperContent+remindThing);
    }
}
    @Test
    public void testSupperClient(){
        //創(chuàng)建目標(biāo)
        ConcreteSupperSubject supperSubject = new ConcreteSupperSubject();
        //創(chuàng)建觀察者
        ConcreteObserver zhangsan = new ConcreteObserver();
        zhangsan.setObserverName("張三");
        zhangsan.setRemindThing("雞翅真好吃!");
        ConcreteObserver lisi = new ConcreteObserver();
        lisi.setObserverName("李四");
        lisi.setRemindThing("夜宵不錯(cuò),吃飽了可以好好干活了");
        //注冊(cè)
        supperSubject.attach(zhangsan);
        supperSubject.attach(lisi);
        //發(fā)布 雞翅,漢堡
        supperSubject.setSupperContent("漢堡");
    }
image.png
image.png
觀察者模式總結(jié)
  1. 存在目標(biāo)對(duì)象和觀察者兩個(gè)概念,做到解耦,但通知卻依賴了抽象的觀察者,假如觀察者無法抽象就無法通知更新。
  2. 所有的觀察者的動(dòng)作都一樣。如果不一樣就不能實(shí)現(xiàn)

事件機(jī)制

事件機(jī)制 就可以解決以上的問題,不需要觀察者的抽象。通過相應(yīng)的listener代替觀察者,類似觀察者模式卻解耦目標(biāo)和觀察。一般把事件對(duì)象作為業(yè)務(wù)接口的參數(shù),再根據(jù)相應(yīng)的條件觸發(fā)
事件機(jī)制一般需要3個(gè)角色,事件觸發(fā)源(source)、事件狀態(tài)對(duì)象(event)、處理邏輯(listener)
在spirng 中ServletContextListener接口通過web中的listener就可以在web啟動(dòng)時(shí)初始化spirng也是事件的一種應(yīng)用。

  1. 定義事件類
//定義的事件狀態(tài)類 也可以繼承java util 中的event
public class MyEvent {

    //事件對(duì)象
    private Object obj;
    //狀態(tài)即觸發(fā)條件
    private String state;

    /**
     * Constructs a prototypical MyEvent.
     *
     * @param source The object on which the MyEvent initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public MyEvent(Object source, String state) {
        //super(source);
        this.obj = source;
        this.state = state;
    }
}
  1. 定義事件源handler
public class EventSource {

    //保存監(jiān)聽器的列表,類似觀察者模式中保存所有觀察者的集合;子類可以保存自己的監(jiān)聽器。
    private Collection listenets;

    //注冊(cè)監(jiān)聽器
    public void addEventListener(EventListener e){
        if (listenets == null){
            listenets = new HashSet();
        }
        listenets.add(e);
    }

    //刪除監(jiān)聽
    public void removeListener(EventListener e){
        if (listenets != null){
            listenets.remove(e);
        }
    }


    /** 根據(jù)觸發(fā)的條件進(jìn)行事件的執(zhí)行
     * @param state 觸發(fā)條件
     */
    protected void fireEvent(String state){
        if (listenets != null){
            MyEvent event = new MyEvent(this,state);
            notifyA(event);
        }
    }

    /**
     * 事件具體的執(zhí)行
     * @param e 定義的事件
     */
    public void notifyA(MyEvent e){
        Iterator iterator = listenets.iterator();
        while (iterator.hasNext()){
            //實(shí)例監(jiān)聽器對(duì)象,并調(diào)用監(jiān)聽器的方法
            EventListener evt = (EventListener) iterator.next();
            evt.handEvent(e);
        }
    }
}
  1. 定義監(jiān)聽器,把事件作為參數(shù),也是和觀察者模式的區(qū)別
public interface EventListener{
    //把事件對(duì)象作為參數(shù)
    void handEvent(MyEvent e);
}
  1. 實(shí)現(xiàn)listener 接口,定義業(yè)務(wù)邏輯
public class Mylistener implements EventListener {


    @Override
    public void handEvent(MyEvent e) {
        //todo 使用if做邏輯控制
        if (e.getState() != null && e.getState() == "hi" ){
            System.out.println(" hi ,java事件機(jī)制");
        }
    }
}
public class Mylistener2 implements EventListener {
    @Override
    public void handEvent(MyEvent e) {
        if (e.getState() != null && e.getState().equals("h")){
            System.out.println(" 我是被 "+e.getState()+" 觸發(fā)的");
        }
    }
}
  1. 測(cè)試
 public static void main(String[] args){
        //事件源
        EventSource source = new EventSource();

        /*
        * 當(dāng)有需要的事件要被觸發(fā)時(shí)就需要編寫并注冊(cè) 相關(guān)的listener
        * 這也是事件的弊端
        * */
        source.addEventListener(new Mylistener());
        source.addEventListener(new Mylistener2());
        //根據(jù)狀態(tài)觸發(fā)
        source.fireEvent("hi");
        source.fireEvent("h");

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

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

  • 以下是消息隊(duì)列以下的大綱,本文主要介紹消息隊(duì)列概述,消息隊(duì)列應(yīng)用場(chǎng)景和消息中間件示例(電商,日志系統(tǒng))。 本次分享...
    文檔隨手記閱讀 1,940評(píng)論 0 28
  • 簡(jiǎn)介 ActiveMQ 特點(diǎn) ActiveMQ 是由 Apache 出品的一款開源消息中間件,旨在為應(yīng)用程序提供高...
    預(yù)流閱讀 6,017評(píng)論 4 21
  • 一、 消息隊(duì)列概述 消息隊(duì)列中間件是分布式系統(tǒng)中重要的組件,主要解決應(yīng)用耦合、異步消息、流量削鋒等問題。實(shí)現(xiàn)高性能...
    步積閱讀 57,462評(píng)論 10 138
  • “ 消息隊(duì)列已經(jīng)逐漸成為企業(yè)IT系統(tǒng)內(nèi)部通信的核心手段。它具有低耦合、可靠投遞、廣播、流量控制、最終一致性等一系列...
    落羽成霜丶閱讀 4,304評(píng)論 1 41
  • Medium App 把首頁(yè)的關(guān)注、編輯推薦、熱門等板塊,由原先的 scrollable tab bar改成了小 ...
    劉英滕閱讀 524評(píng)論 1 4

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