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

語言: CN / TW / HK

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

程式碼背景

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

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

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


觀察者模式

介紹

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

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

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

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

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

實現

img

觀察者(學生)

``` /* * 抽象的觀察者 * * @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("-----------");
}

} ```

img


委託

介紹

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

一個委託可以搭載多個方法,所有的方法被依次喚起。可以使委託物件所搭載的方法並不需要屬於同一類。

委託事件模型可以由三個元件定義:事件、事件源和事件偵聽器。

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

實現

img

觀察者

``` /* * 監聽器/觀察者 玩遊戲 * 事件監聽器 * @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();
}

} ```

img


總結

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類的學生

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

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

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