天天看點

回調模式、事件監聽器模式、觀察者模式、指令模式

回調函數

回調函數是了解監聽器、觀察者模式的關鍵。

所謂的回調,用于回調的函數。 回調函數隻是一個功能片段,由使用者按照回調函數調用約定來實作的一個函數。

舉個例子:

這裡有兩個實體:回調抽象接口、回調者(即程式a)

回調接口(ICallBack )

public interface ICallBack {
    public void callBack();
}
           

回調者(用于調用回調函數的類)

public class Caller {

    public void call(ICallBack callBack){
        System.out.println("start...");
        callBack.callBack();
        System.out.println("end...");
    }

}
           

回調測試:

public static void main(String[] args) {
       Caller call = new Caller();
       call.call(new ICallBack(){
        @Override
        public void callBack() {
            System.out.println("終于回調成功了!");

        } 
       });

}
           

控制台輸出:

start...
終于回調成功了!
end...
           

還有一種寫法

ICallBack callBackB = new ICallBack(){
        @Override
        public void callBack() {
            System.out.println("終于回調成功了!");
        }           
};
call.call(callBackB);
           

或實作這個ICallBack接口類

class CallBackC implements ICallBack{
        @Override
        public void callBack() {
            System.out.println("終于回調成功了!");  
        }
}
           

有沒有發現這個模型和執行一個線程,Thread很像。 沒錯,Thread就是回調者,Runnable就是一個回調接口。

new Thread(new Runnable(){

@Override

public void run() {

System.out.println(“回調一個新線程!”);

}}).start();

Callable也是一個回調接口,原來一直在用。 接下來我們開始講事件監聽器

二、事件監聽模式

監聽器,字面上的了解就是監聽觀察某個事件(程式)的發生情況,當被監聽的事件真的發生了的時候,事件發生者(事件源) 就會給注冊該事件的監聽者(監聽器)發送消息,告訴監聽者某些資訊,同時監聽者也可以獲得一份事件對象,根據這個對象可以獲得相關屬性和執行相關操作。

回調模式、事件監聽器模式、觀察者模式、指令模式

Java的事件監聽機制可概括為3點:

Java的事件監聽機制涉及到事件源,事件監聽器,事件對象三個元件,監聽器一般是接口,用來約定調用方式。

當事件源對象上發生操作時,它将會調用事件監聽器的一個方法,并在調用該方法時傳遞事件對象過去。

事件監聽器實作類,通常是由開發人員編寫,開發人員通過事件對象拿到事件源,進而對事件源上的操作進行處理。

舉個例子

這裡我為了友善,直接使用JDK,EventListener 監聽器,感興趣的可以去研究下源碼,非常簡單。

監聽器接口

public interface EventListener extends java.util.EventListener {
    //事件處理
    public void handleEvent(EventObject event);
}
           

事件對象

public class EventObject extends java.util.EventObject{
    private static final long serialVersionUID = 1L;
    public EventObject(Object source){
        super(source);
    }
    public void doEvent(){
        System.out.println("通知一個事件源 source :"+ this.getSource());
    }

}
           

事件源

事件源是事件對象的入口,包含監聽器的注冊、撤銷、通知

public class EventSource {
   //監聽器清單,監聽器的注冊則加入此清單
    private Vector<EventListener> ListenerList = new Vector<EventListener>();
    //注冊監聽器
    public void addListener(EventListener eventListener){
        ListenerList.add(eventListener);
    }
    //撤銷注冊
    public void removeListener(EventListener eventListener){
        ListenerList.remove(eventListener);
    }
 //接受外部事件
    public void notifyListenerEvents(EventObject event){        
        for(EventListener eventListener:ListenerList){
                eventListener.handleEvent(event);
        }
    }

}
           

測試執行

public static void main(String[] args) {
        EventSource eventSource = new EventSource();

        eventSource.addListener(new EventListener(){
            @Override
            public void handleEvent(EventObject event) {
                event.doEvent();
                if(event.getSource().equals("closeWindows")){
                    System.out.println("doClose");
                } 
            }

        });


        /*
         * 傳入openWindows事件,通知listener,事件監聽器,
         對open事件感興趣的listener将會執行
         **/
        eventSource.notifyListenerEvents(new EventObject("openWindows"));

}
           

控制台顯示:

通知一個事件源 source :openWindows

通知一個事件源 source :openWindows

doOpen something...
           

到這裡你應該非常清楚的了解,什麼是事件監聽器模式了吧。 那麼哪裡是回調接口,哪裡是回調者,對!EventListener是一個回調接口類,handleEvent是一個回調函數接口,通過回調模型,EventSource 事件源便可回調具體監聽器動作。

有了了解後,這裡還可以做一些變動。 對特定的事件提供特定的關注方法和事件觸發

public class EventSource {
     ...
  public void onCloseWindows(EventListener eventListener){
        System.out.println("關注關閉視窗事件");
        ListenerList.add(eventListener);
    }

    public void doCloseWindows(){
        this.notifyListenerEvents(new EventObject("closeWindows"));
    }
    ...
}
public static void main(String[] args) {
   EventSource windows = new EventSource();
        /**
         * 另一種實作方式
         */
        //關注關閉事件,實作回調接口
        windows.onCloseWindows(new EventListener(){

            @Override
            public void handleEvent(EventObject event) {
                event.doEvent();
                if(event.getSource().equals("closeWindows")){
                    System.out.println("通過onCloseWindows來關注關閉視窗事件:并執行成功。 closeWindows");
                }

            }

        });

       //視窗關閉動作
        windows.doCloseWindows();

}
           

這種就類似于,我們的視窗程式,Button監聽器了。我們還可以為單擊、輕按兩下事件定制監聽器。

三、觀察者模式

觀察者模式指在被觀察者的狀态發生變化時,系統基于事件驅動理論将其狀态通知到訂閱其狀态的觀察者對象中,以完成狀态的修改和事件傳播。觀察者模式是一種對象行為模式,觀察者和被觀察者之間的關系屬于抽象耦合關系,主要優點是觀察者與被觀察者之間建立了一套事件觸發機制,以降低二者之間的耦合度。

觀察者模式的主要角色如下:

抽象主題Subject:持有訂閱了該主題的觀察者對象的集合,同時提供了增加删除觀察者對象的方法和主題狀态變化後的通知方法

具體主題Concrete Subject:實作了抽象主題的通知方法,在主題内部狀态發生變化時,調用該方法通知訂閱了主題狀态的觀察者對象

抽象觀察者Observer:觀察者的抽象類或接口,定義了主題狀态變化時需要調用的方法

具體觀察者 Concrete Observer:抽象觀察者的實作類,在收到主題狀态變化的資訊後執行具體觸發機制

使用場景

既然用到了觀察者模式,我們就是希望一個事件出來了,會有多個不同的類需要處理相應的資訊。比如,訂單修改成功事件,我們希望發短信的類得到通知、發郵件的類得到通知、處理物流資訊的類得到通知等。

當然,jdk 也提供了相似的支援,具體的大家可以參考 java.util.Observable 和 java.util.Observer 這兩個類。

實際生産過程中,觀察者模式往往用消息中間件來實作,如果要實作單機觀察者模式,筆者建議讀者使用 Guava 中的 EventBus,它有同步實作也有異步實作

(1)定義抽象主題Subject

public abstract class Subject {
    protected List<Observer> observers=new ArrayList<>();

    public void add(Observer observer){
        observers.add(observer);
    }

    public void remove(Observer observer){
        observers.remove(observer);
    }

    public abstract void notify(String message);
}
           

(2)定義具體主題ConcreteSubject

public class ConcreteSubject extends Subject {
    @Override
    public void notify(String message) {
        for(Object obj:observers){
            System.out.println("通知觀察者 "+message);
            ((Observer)obj).dataChange(message);
        }
    }
}
           
(3)定義抽象觀察者
           
public interface Observer {
    
    void dataChange(String message);
}
           

(4)定義具體的觀察者

public class ConcreteObserver implements Observer {


    @Override
    public void dataChange(String message) {
        System.out.println("收到資訊 "+message);
    }
}
           

(5)使用觀察者模式

public static void main(String[] args) {
        Subject subject=new ConcreteSubject();
        Observer obs=new ConcreteObserver();
        subject.add(obs);
        subject.notify("lakers win");
    }
           

運作結果如下:

回調模式、事件監聽器模式、觀察者模式、指令模式

指令模式 Command

指令模式将請求封裝為指令基于事件驅動異步執行,以實作指令的發送者和指令的執行者之間的解耦,提高指令發送執行的效率和靈活度。指令模式主要包含以下角色:

抽象指令類

執行指令的接口,定義執行指令的抽象方法

具體指令類

抽象指令類的實作類,持有接收者對象,并在收到指令後調用指令執行者的方法doCommand()實作指令的調用和執行

指令執行者

指令的具體執行者,定義了指令執行的具體方法doCommand()

指令調用者

接收用戶端的指令并異步執行

//1.指令的抽象接口
public interface Command{
  void doCommand(){
  }
}
//2.指令接收者
public class Receiver{
  public void receiveDoA(){
  //執行具體的操作
  }
  public void receiveDoB(){
  //執行具體的操作
  }
}
//3.具體的指令
public class CommandImplA implements Command{
  private Receive receiver;
  public CommandImplA(Receiver receiver){
      this.receiver = receiver;
  }
  @Override
  void doCommand(){
     receive.receiveDoA();
  }
}
public class CommandImplB implements Command{
  private Receive receiver;
  public CommandImplB(Receiver receiver){
      this.receiver = receiver;
  }
  @Override
  void doCommand(){
     receive.receiveDoB();
  }
}
//4.指令的發送者
public class Sender{
  private Command command;
  public Sender(Command command){
    this.command = command;
  }
  public void sendCommand(){
    command.doCommand();
  }
}
//5.用戶端使用
public class Client{
  public static void main(String[] args){
    Receiver receiver = new Receiver();
    CommandImplA cmdA = new CommandImplA(receive);
    CommandImplB cmdB = new CommandImplB(receive);
    Sender sender = new Sender(cmdA);
    sender.sendCommand();
  }
}
           

指令模式是将接收者要做的事情,抽象成指令,通過指令來解耦。在具體的指令中,用指令所引用的接收者執行個體進行相應的操作。指令的發送者負責發送各種指令。

總結

從整個實作和調用過程來看,觀察者和監聽器模式基本一樣。 說到底,就是事件驅動模型,将調用者和被調用者通過一個連結清單、回調函數來解耦掉,互相獨立。

整個設計模式的初衷也就是要做到低耦合,低依賴。

再延伸下,消息中間件是什麼一個模型?将生産者+服務中心(事件源)和消費者(監聽器)通過消息隊列解耦掉. 消息這相當于具體的事件對象,隻是存儲在一個隊列裡(有消峰填谷的作用),服務中心回調消費者接口通過拉或取的模型響應。 想必基于這個模型,實作一個簡單的消息中間件也是可以的。

還比如Guava ListenableFuture,采用監聽器模式就解決了future.get()一直阻塞等待傳回結果的問題。

有興趣的同學,可以再思考下觀察者和責任鍊之間的關系, 我是這樣看的。

同樣會存在一個連結清單,被觀察者會通知所有觀察者,觀察者自行處理,觀察者之間互不影響。 而責任鍊,講究的是擊鼓傳花,也就是每一個節點隻需記錄繼任節點,由目前節點決定是否往下傳。 常用于工作流,過濾器Web Filter。

繼續閱讀