设计模式—观察者模式升级发布订阅

127 阅读5分钟

前言

观察者是一种行为设计模式,它定义了对象间一对多\large\textcolor{seagreen}{对象间一对多}的关系:当一个对象发生变化,依赖它的多个对象都会收到通知并自动更新。

观察者模式.png 猫咪可以晒着太阳睡觉,邻居在家看书,物业监控中心看着每一个进出的人。这些观察者都各自做着其他的事,不用一直盯着7号楼11层业主是否唱歌。被观察者一旦唱歌,这个观察者都会听到“执行”自己的update()。

观察者:只需要有一个对“感兴趣事件”的反应方式update();

被观察者:负责通知观察者执行它的update();管理观察者(ArrayList),注册,删除

被观察者

G2高铁到达南京站,两位到南京的乘客要下车。对于G2到达南京这件事,乘客Passenger1、Passenger2都很感兴趣,但它们不能在两个小时里啥也不做,就盯着是否到达南京。期间它们可以刷视频、看小说。到达南京站,列车会报站通知。乘客下车。 高铁有很多,所以我们需要把高铁共同点抽象出来成为subject:管理旅客列表(add,remove),报站(notify)

主题subject

public interface Subject_主题or被观察者or高铁_EventSource {
    void addPassenger(Observer_or观察者or旅客or_Listener passenger);
    void removePassenger(Observer_or观察者or旅客or_Listener passenger);
    void notifyPassenger(ArriveStationEvent ae) throws InterruptedException;
}

具体被观察者-G2

到了具体被观察者,就需要一个管理观察者注册的列表

public class G2 implements Subject_主题or被观察者or高铁_EventSource{
    public ArrayList<Observer_or观察者or旅客or_Listener> passengerActions = new ArrayList<>();

    @Override
    public void addPassenger(Observer_or观察者or旅客or_Listener passenger) {
        passengerActions.add(passenger);
    }

    @Override
    public void removePassenger(Observer_or观察者or旅客or_Listener passenger) {
        passengerActions.remove(passenger);
    }

    @Override
    public void notifyPassenger(ArriveStationEvent ae) throws InterruptedException {
        Thread.sleep(2000);//模拟收拾行李下车
        for(int i=0;i<passengerActions.size();i++){
            passengerActions.get(i).getOff(ae);
        }
    }
}

Observer

观察者抽象出来对兴趣事件的反应方式(乘客上下车)

public interface Observer_or观察者or旅客or_Listener {
    void getOn(ArriveStationEvent ae);
    void getOff(ArriveStationEvent ae);
}

具体观察者

具体观察者实现自己不同的反应,一个拿行李,另一个拿手机电源

public class Passenger1 implements Observer_or观察者or旅客or_Listener{
    @Override
    public void getOn(ArriveStationEvent ae) {
        System.out.println("我是Passenger1上车啦!站台信息:"+ae.getStartOffStation());
    }

    @Override
    public void getOff(ArriveStationEvent ae) {
        System.out.println("拿上行李,Passenger1下车啦!站台信息:"+ae.getArriveStation());
    }
}

public class Passenger2 implements Observer_or观察者or旅客or_Listener{
    @Override
    public void getOn(ArriveStationEvent ae) {
        System.out.println("我是Passenger2上车啦!车辆信息:"+ae.getStartOffStation());
    }

    @Override
    public void getOff(ArriveStationEvent ae) {
        System.out.println("带上电源线,我是Passenger2下车啦!车辆信息:"+ae.getArriveStation());
    }
}

调用

加入线程,实现异步,模拟了,列车到站,通知完乘客,广播还可以播报广告。乘客下车和列车广播实现了真正的异步。

public class Station {
    public String stationname = "南京";
    public static void main(String[] args) {
        G2 g2 = new G2();
        g2.addPassenger(new Passenger1());
        g2.addPassenger(new Passenger2());
        //乘客在车上不用盯着高铁是否到达南京,可以玩手机,到达南京站,高铁会报站让乘客下车
        ArriveStationEvent ae = new ArriveStationEvent();
        ae.setArriveStation("南京");
        new Thread(() -> {
            try {
                g2.notifyPassenger(ae);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        System.out.println("列车开始播报南京旅游广告:南京中山陵、秦淮河");
    }
}

屏幕截图 2025-05-19 105250.png

说明

下面就是一个数据载体,在事件驱动里的Event携带了事件发生时的主要数据内容。

public class ArriveStationEvent {
    public String startOffStation;
    public String arriveStation;

    public String getStartOffStation() {
        return startOffStation;
    }

    public void setStartOffStation(String startOffStation) {
        this.startOffStation = startOffStation;
    }

    public String getArriveStation() {
        return arriveStation;
    }

    public void setArriveStation(String arriveStation) {
        this.arriveStation = arriveStation;
    }
}

总结

无论是是观察者,被观察者,都有有一个父接口,这是面向对象的关键抽象,将共同行为抽取出来,新的参与者进来,对于这些共性方法调用的地方不用改变。我们观察者模式的关键在于,将观察者和状态监控解耦

状态监控耦合(主动轮询 vs 被动通知)

// 未解耦时的主动轮询(反模式)
while(true) {
    if(train.getCurrentStation().equals("北京")) {
        disembark(); // 需要持续检查状态
    }
}

观察者模式通过将「状态监控」和「状态响应」分离

  • 被观察者:专注状态管理
  • 观察者:专注响应处理
    无需观察者主动查询状态
graph LR
    A[被观察者] -->|维护| B(观察者列表)
    B --> C[Observer接口]
    C --> D{具体观察者}
    C --> E{其他观察者}
    
    style A fill:#f9f,stroke:#333
    style C fill:#b9f,stroke:#333

发布订阅

上面我们已经看到了,乘客有上车也有下车,如果一个乘客上车,一个乘客下车,怎么实现?维护两个观察者列表,上车下车?那不同站上下车了呢?列车维护每个站列表(上下车)?

graph LR
    A[被观察者_G2高铁] -->|维护| B(观察者_南京下车乘客列表)
    A[被观察者_G2高铁] -->|维护| J(观察者_南京上车乘客列表)
    A[被观察者_G2高铁] -->|维护| H(观察者_济南下车乘客列表)
    A[被观察者_G2高铁] -->|维护| G(观察者_济南上车乘客列表)
    H --> C[Observer接口]
    B --> C[Observer接口]
    G --> C[Observer接口]
    J --> C[Observer接口]
    C --> D{具体观察者_济南西下车}
    C --> I{具体观察者_济南西上车}
    C --> E{其他观察者_南京下车}
    C --> Q{其他观察者_南京上车}
    
    style A fill:#f9f,stroke:#333
    style C fill:#b9f,stroke:#333

观察者上下车,车站还需要列车到站信息组织检票,这都作为一个个列表交给G2维护?岂不是累死。所以把列表维护提取出来,彻底解耦观察者和被观察者之间的联系。那么这个观察者列表集就由消息队列broker来实现。broker来管理观察者的订阅,接收被观察者产生的消息,通知观察者。你订阅了南京站下车消息,列车到南京站,它只用发送一个南京站到达的消息给broker,broker分发这个消息给站务工作人员,南京站下车的乘客收到短信,站内等待上车的旅客也会收到短信。发布订阅核心就是将观察者列表的维护和通知提取出来交给中间件

graph LR
%% 观察者模式结构 %%
subgraph Observer[观察者模式]
  A(Subject被观察者)--维护-->B[Observer List]
  B-->C(ObserverA)
  B-->D(ObserverB)
end

%% 发布-订阅模式结构 %%
subgraph PubSub[发布-订阅模式]
  E(Publisher发布者)--发布事件-->F[[broker]]
  F--路由事件-->G(SubscriberA)
  F--路由事件-->H(SubscriberB)
  F-.动态绑定.->I(New Subscriber)
end

style F fill:#6f9,stroke:#333