Java中觀察者模式與委託,還在傻傻分不清

語言: CN / TW / HK
摘要:本文通過對比Java中觀察者模式與委託,希望能夠讓開發者分清二者的區別和聯絡。

本文分享自華為雲社群《Java中觀察者模式與委託的對比》,作者: 小小張自由--張有博 。

程式碼背景

一個班級,有兩類學生,A類:不學習,玩,但是玩的東西不一樣,有的是做遊戲,有的是看電視

B類:放哨的學生,專門看老師的動向,如果老師進班了就立即通知大家。

如此就形成了一個需求,放哨的學生要通知所有玩的學生:老師來了,而不同的學生有不同的反應,有的馬上把電視關閉,有的停止玩遊戲。

觀察者模式

介紹

觀察者模式:定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有的觀察者物件,使他們能夠自動更新自己。

主要解決:一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作。

何時使用:一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。

如何解決:使用面向物件技術,可以將這種依賴關係弱化。

關鍵程式碼:在抽象類裡有一個 ArrayList 存放觀察者們。

實現

觀察者(學生)

/**
 * 抽象的觀察者
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:32
 */
public interface Observer {

    public abstract void updateState();

}

/**
 * 具體的觀察者
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:39
 */
public class ConcreteObserver implements Observer{

    //觀察者的姓名
    private String name;

    //觀察者的狀態
    private String observerState;

    //明確具體的通知者
    private ConcreteSubject subject;

   //get set方法省略

    public ConcreteObserver(String name, ConcreteSubject subject) {
        this.name = name;
        this.subject = subject;
    }

    @Override
    public void updateState() {
        observerState=subject.getSubjectState();
        System.out.println(name+"在打遊戲");
        String str=String.format("觀察者%s的:新狀態是%s", name,observerState);
        System.out.println(str);
    }
}
/**
 * 具體的觀察者
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:39
 */
public class ConcreteObserver2 implements Observer{

    //觀察者的姓名
    private String name;

    //觀察者的狀態
    private String observerState;

    //明確具體的通知者
    private ConcreteSubject subject;

   //get set方法省略

    public ConcreteObserver2(String name, ConcreteSubject subject) {
        this.name = name;
        this.subject = subject;
    }

    @Override
    public void updateState() {
        observerState=subject.getSubjectState();
        System.out.println(name+"在看電視");
        String str=String.format("觀察者%s:新狀態是%s", name,observerState);
        System.out.println(str);
    }
}

通知者(老師)

/**
 * 抽象的通知者
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:30
 */
public abstract class Subject {

    //管理觀察者的集合
    private List<Observer> observers=new ArrayList<>();

    //增加觀察者
    public void add(Observer observer){
        observers.add(observer);
    }

    //減少觀察者
    public void detach(Observer observer){
        observers.remove(observer);
    }
    /**
     * 通知所有的觀察者
     */
    public void notifyMsg(){
        for (Observer observer : observers) {
            observer.updateState();
        }
    }

}
/**
 * 具體的通知者
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:38
 */
public class ConcreteSubject extends Subject {

    //通知者的狀態
    private String subjectState;

    //get set方法
    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
    }
}

Main方法

/**
 * 控制檯Main方法
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/10 - 15:48
 */
public class MainTest {

    public static void main(String[] args) {
        //建立一個主題/通知者
        ConcreteSubject subject=new ConcreteSubject();

        //new出觀察者(學生)
        ConcreteObserver studentZhang = new ConcreteObserver("小張", subject);
        ConcreteObserver studentLiu = new ConcreteObserver("小劉", subject);
        ConcreteObserver studentWang = new ConcreteObserver("小王", subject);

        //將觀察者新增到通知佇列裡
        subject.add(studentZhang);
        subject.add(studentLiu);
        subject.add(studentWang);

        //通知者(老師)狀態修改,通知每個學生
        subject.setSubjectState("老師回來了,我要好好學習");
        subject.notifyMsg();

        System.out.println("-----------");
    }

}

委託

介紹

委託可以看做是函式的抽象,是函式的“類”。委託的例項將代表一個具體的函式。

一個委託可以搭載多個方法,所有的方法被依次喚起。可以使委託物件所搭載的方法並不需要屬於同一類。
委託事件模型可以由三個元件定義:事件、事件源和事件偵聽器。

委託的實現簡單來講就是用反射來實現的。

實現

觀察者

/**
 * 監聽器/觀察者 玩遊戲
 * 事件監聽器
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:17
 */
public class PlayingGameListener {

    public PlayingGameListener(){
        System.out.println("我正在玩遊戲 開始時間"+new Date());
    }
    public void stopPlayingGame(Date date){
        System.out.println("老師來了,快回到座位上,結束時間"+date);
    }

}

/**
 * 監聽器/觀察者 看電視
 * 事件監聽器
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:17
 */
public class WatchingTVListener {

    public WatchingTVListener(){
        System.out.println("我正在看電視 "+new Date());
    }
    public void stopWatchingTV(Date date){
        System.out.println("老師來了,快關閉電視 。 結束時間"+date);
    }
}

通知者

/**
 * 通知者的抽象類
 * 事件源
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:15
 */
public abstract class Notifier {

    //每個通知者都有一個需要通知的佇列(通知:物件、方法、引數)
    private EventHandler eventHandler=new EventHandler();

    public EventHandler getEventHandler() {
        return eventHandler;
    }

    public void setEventHandler(EventHandler eventHandler) {
        this.eventHandler = eventHandler;
    }

    //增加需要幫忙放哨的學生
    public abstract void addListener(Object object,String methodName,Object...args);
    //告訴所有要幫忙放哨的學生:老師來了
    public abstract void notifyX();
}
/**
 * 通知者的子類,放哨人
 * 事件源
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:15
 */
public class GoodNotifier extends Notifier {

    @Override
    public void addListener(Object object, String methodName, Object...args) {
        System.out.println("有新的同學委託盡職盡責的放哨人!");
        this.getEventHandler().addEvent(object, methodName, args);
    }

    @Override
    public void notifyX() {
        System.out.println("盡職盡責的放哨人告訴所有需要幫忙的同學:老師來了");
        try{
            //優化:非同步通知
            this.getEventHandler().notifyX();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

事件

/**
 * 抽象出的事件類,也可以稱為方法類
 * 事件
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:03
 */
public class Event {

    //要執行方法的物件
    private Object object;
    //要執行的方法名稱
    private String methodName;
    //要執行方法的引數
    private Object[] params;
    //要執行方法的引數型別
    private Class[] paramTypes;

    //若干setter getter
    public Object getObject() {
        return object;
    }

    public String getMethodName() {
        return methodName;
    }
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParams() {
        return params;
    }
    public void setParams(Object[] params) {
        this.params = params;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }
 
    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Event(){

    }
    public Event(Object object,String methodName,Object...args){
        this.object=object;
        this.methodName=methodName;
        this.params=args;
        contractParamTypes(this.params);
    }

    //根據引數陣列生成引數型別陣列
    private void contractParamTypes(Object[] params){
        this.paramTypes=new Class[params.length];
        for(int i=0;i<params.length;i++){
            this.paramTypes[i]=params[i].getClass();
        }
    }
    //執行該 物件的該方法
    public void invoke() throws Exception{
        //通過class,method,paramTypes 確定執行哪個類的哪個方法
        Method method=object.getClass().getMethod(this.getMethodName(), this.getParamTypes());
        if(null==method){
            return;
        }
        //方法執行
        method.invoke(this.getObject(), this.getParams());
    }

}

事件處理

/**
 * 管理哪些事件需要執行
 * 管理事件
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:03
 */
public class EventHandler {

    //是用一個List
    private List<Event> objects;
    //新增某個物件要執行的事件,及需要的引數
    public void addEvent(Object object,String methodName,Object...args){
        objects.add(new Event(object,methodName,args));
    }

    public EventHandler(){
        objects=new ArrayList<Event>();
    }

    //通知所有的物件執行指定的事件
    public void notifyX() throws Exception{
        for(Event e : objects){
            e.invoke();
        }
    }
}

Main方法

/**
 * 啟動類
 *
 * @author Promsing(張有博)
 * @version 1.0.0
 * @since 2022/5/8 - 11:19
 */
public class EventMain {

    public static void main(String[] args) {
        //建立一個盡職盡責的放哨者
        Notifier goodNotifier = new GoodNotifier();

        //建立一個玩遊戲的同學,開始玩遊戲
        PlayingGameListener playingGameListener = new PlayingGameListener();
        //建立一個看電視的同學,開始看電視
        WatchingTVListener watchingTVListener = new WatchingTVListener();
        //玩遊戲的同學告訴放哨的同學,老師來了告訴一下
        goodNotifier.addListener(playingGameListener, "stopPlayingGame", new Date());
        //看電視的同學告訴放哨的同學,老師來了告訴一下
        goodNotifier.addListener(watchingTVListener, "stopWatchingTV", new Date());
        try {
            //一點時間後
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //老師出現,放哨的人通知所有要幫忙的同學:老師來了
        goodNotifier.notifyX();
    }
}

總結

1.先有觀察者模式後有委託事件技術
2.觀察者模式只能通知繼承 Observer類 的子類,也可以將Observer改成介面

for (Observer observer : observers) {
        observer.updateState();
}

3.委託可以通知任何類的任何方法。反射、everone

    Method method=object.getClass().getMethod(this.getMethodName(), this.getParamTypes());
    if(null==method){
        return;
    }
    method.invoke(this.getObject(), this.getParams());

4.委託與觀察者比多了一個事件執行者,解除觀察者與通知者的耦合,可以做到通知任何物件的任何方法。讓A類學生和B類學生完全解耦,即A類完全不知道B類的學生,卻可以通知B類的學生

5.建立一套觸發機制,可以使用非同步通知

6.觀察者/委託挺像MQ裡邊的訂閱釋出。生產者、佇列、消費者。

 

點選關注,第一時間瞭解華為雲新鮮技術~