设计模式第六章(观察者模式)

设计模式第六章(观察者模式)

​ 观察者模式是一种行为设计模式,它定义了对象之间的一对对多依赖关系:当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。

这种模式的核心思想是解耦被观察者和观察者,让它们可以独立变化,同时保持联动。

前言

关键角色

  1. 被观察者(Observable/Subject)
    • 维护一个观察者列表,提供添加、移除观察者的方法
    • 当自身状态变化时,主动通知所有注册的观察者
  2. 观察者(Observer)
    • 定义一个更新接口,当收到被观察者的通知时,执行相应的处理逻辑
    • 可以有多个不同的观察者实现,各自处理方式不同

工作流程

  1. 观察者通过被观察者提供的方法(如 addObserver())注册自己
  2. 被观察者状态发生改变时,调用自身的通知方法(如 notifyObservers()
  3. 被观察者遍历所有注册的观察者,调用它们的更新方法(如 update()
  4. 观察者收到通知后,根据被观察者的状态变化执行具体操作

典型场景

  • 消息订阅:公众号(被观察者)更新文章后,所有订阅者(观察者)收到推送
  • 数据监控:传感器(被观察者)检测到温度变化,仪表盘、报警器(观察者)分别响应
  • GUI 事件处理:按钮(被观察者)被点击时,关联的回调函数(观察者)执行

优点

  • 降低耦合:被观察者无需知道观察者的具体实现,只需调用统一的更新接口
  • 扩展性好:新增观察者时,无需修改被观察者代码,符合 "开闭原则"
  • 联动灵活:可以动态添加 / 移除观察者,实时调整响应关系

例如,在天气系统中:

  • 气象站(被观察者)收集到温度变化
  • 手机 APP、显示屏、报警器(多个观察者)会同时收到通知并更新显示或触发警报
  • 若后续需要添加新的观察者(如恒温控制器),只需让它实现观察者接口并注册即可,无需修改气象站代码。

实战第一版本

故事背景:一个天气站如果更新了天气,需要将天气通知订阅了天气更新的某些人和事。我们来用一个初级版本直接堆功能来实现。

用户信息

java 复制代码
public class User {

    private final String name;

    private final Consumer<String> consumer;

    public User(String name, Consumer<String> consumer) {
        this.name = name;
        this.consumer = consumer;
    }


    public void notify(String inf) {
        consumer.accept(inf);
    }
}

天气站

java 复制代码
public class WeatherStation {


    private final List<User> users = new ArrayList<User>();

    public void addUser(User user) {
        users.add(user);
    }


    public String getInfo() {
        if (new Random().nextBoolean()) {
            return "晴天";
        }
        return "雨天";
    }


    public void start() {
        while (true) {
            String info = getInfo();
            for (User user : users) {
                user.notify(info);
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试用例

java 复制代码
public class Main {
    public static void main(String[] args) {
        //TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
        // to see how IntelliJ IDEA suggests fixing it.
        System.out.println("Hello and welcome!");

        User tom = new User("tom", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,tom快出来玩啊~~~");
            } else {
                System.out.println("下雨天,tom 你需要在家待着了~~~");
            }
        });

        User jerry = new User("jerry", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,jerry快出来游泳了~~");
            }
        });

        //订阅天气信息
        WeatherStation station = new WeatherStation();
        station.addUser(tom);
        station.addUser(jerry);

        station.start();


        // 问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢,
        // 通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢
        // 接下来,我们就需要一个tvstation来进行通知


    }
}

问题点

  • 问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢

  • 通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢

  • 接下来,我们就需要一个tvstation来进行通知

实战第二版本

​ 我们增加了一个电视台的角色,所有的订阅都去电视台,天气信息的更新我只负责通知到电视台,由电视台再通知被订阅的人

用户信息

java 复制代码
public class User {

    private final String name;

    private final Consumer<String> consumer;

    public User(String name, Consumer<String> consumer) {
        this.name = name;
        this.consumer = consumer;
    }


    public void notify(String inf) {
        consumer.accept(inf);
    }
}

电视台

  • 提供一个订阅的入口
  • 提供一个发布的入口
java 复制代码
public class TvStation {

    private final List<User> userList = new ArrayList<>();

    // 订阅的入口
    public void addUser(User user) {
        userList.add(user);
    }


    public void publish(String inf) {
        for (User user : userList) {
            user.notify(inf);
        }
    }

}

天气站

java 复制代码
public class WeatherStation {

    //电视台
    private final TvStation tvStation;

    public WeatherStation(TvStation tvStation) {
        this.tvStation = tvStation;
    }


    public String getInfo() {
        if (new Random().nextBoolean()) {
            return "晴天";
        }
        return "雨天";
    }


    public void start() {
        while (true) {
            String info = getInfo();
            //我只需要把我得消息给到电视台,由电视台进行这个通知的动作
            tvStation.publish(info);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试用例

java 复制代码
public class Main {
    public static void main(String[] args) {
        //TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
        // to see how IntelliJ IDEA suggests fixing it.
        System.out.println("Hello and welcome!");

        User tom = new User("tom", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,tom快出来玩啊~~~");
            } else {
                System.out.println("下雨天,tom 你需要在家待着了~~~");
            }
        });

        User jerry = new User("jerry", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,jerry快出来游泳了~~");
            }
        });

        TvStation tvStation = new TvStation();
        //两个用户订阅了电视台的消息
        tvStation.addUser(tom);
        tvStation.addUser(jerry);

        // 获取天气信息
        WeatherStation weatherStation = new WeatherStation(tvStation);
        weatherStation.start();

        // 天气只负责获取信息,通知由电视台进行
        // 优化点,如果用户需要订阅 增加了一个新闻类的信息该怎么办呢
        // 下一节,我们将抽象发布布订阅模式

    }
}

问题点

  • 如果用户需要订阅 一个新闻类的信息该怎么办呢,我们是不是需要在里面再继续加一个新闻的类的,下一章节使用订阅发布模式来解耦

实战第三版本

  • 事件,所有的需要通知的事件都需要实现该接口。
  • 事件监听,所有希望得到通知的都需要实现该接口

事件总线接口

java 复制代码
public interface Event {


    /**
     *  时间戳
     * @return
     */
    long timestamp();

    /**
     *  数据个格式
     * @return
     */
    Object source();
}

事件总线抽象基类

java 复制代码
public abstract class BaseEvent implements Event {

    @Override
    public long timestamp() {
        return System.currentTimeMillis();
    }
}

天气更新的事件

如果天气更新了,那么我事先事件这个接口,信息都包装为一个 event 事件信息

复制代码
public class WeatherUpdEvent extends BaseEvent{

    //天气信息
    private final String info;

    public WeatherUpdEvent(String info) {
        this.info = info;
    }

    @Override
    public Object source() {
        return info;
    }
}

事件监听

java 复制代码
public interface ListenerEvent {


    /**
     *  监听事件
     * @param event
     */
    void onEvent(Event event);
}

订阅感兴趣的事件

实际为最终消费端。拿到事件,然后做什么

java 复制代码
public class User implements ListenerEvent {

  private final String name;

  private final Consumer<String> consumer;

  public User(String name, Consumer<String> consumer) {
    this.name = name;
    this.consumer = consumer;
  }


  private void notify(String inf) {
    consumer.accept(inf);
  }

  @Override
  public void onEvent(Event event) {
    if (event instanceof WeatherUpdEvent) {
      notify(event.source().toString());
    }
  }


}

事件总线

我们监听订阅了event时间下所有的订阅者。

java 复制代码
public class TvStation {


    private final Map<Class<? extends Event>,List<ListenerEvent>> listenerEventMap = new HashMap<>();


    // 订阅的入口
    public void subscribe(ListenerEvent event,Class<? extends Event> eventType) {
        listenerEventMap.computeIfAbsent(eventType,k -> new ArrayList<>()).add(event);
    }


    public void publish(Event event) {
        Class<? extends Event> evnetClass = event.getClass();
        List<ListenerEvent> listenerEvents = listenerEventMap.get(evnetClass);
        if (listenerEvents != null) {
            for (ListenerEvent listener : listenerEvents) {
                listener.onEvent(event);
            }
        }
    }

}

天气站

我们需要将我们的天气信息包装为一个event 的事件,这个时候就是天气更新的事件,我们进行包装一次。

java 复制代码
public class WeatherStation {

    //电视台  我们成为消息总线
    private final TvStation tvStation;

    public WeatherStation(TvStation tvStation) {
        this.tvStation = tvStation;
    }


    public String getInfo() {
        if (new Random().nextBoolean()) {
            return "晴天";
        }
        return "雨天";
    }


    public void start() {
        while (true) {
            String info = getInfo();
            WeatherUpdEvent weatherUpdEvent = new WeatherUpdEvent(info);
            tvStation.publish(weatherUpdEvent);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试用例

java 复制代码
public class Main {
    public static void main(String[] args) {
        User tom = new User("tom", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,tom快出来玩啊~~~");
            } else {
                System.out.println("下雨天,tom 你需要在家待着了~~~");
            }
        });

        User jerry = new User("jerry", inf -> {
            if (inf.equals("晴天")) {
                System.out.println("晴天了,jerry快出来游泳了~~");
            }
        });


        TvStation tvStation = new TvStation();
        tvStation.subscribe(tom, WeatherUpdEvent.class);
        tvStation.subscribe(jerry, WeatherUpdEvent.class);

        WeatherStation station = new WeatherStation(tvStation);
        station.start();


    }
}

spring使用观察者模式

复制代码
@Autowired
private ApplicationContext context;


// 事件发布器
@Autowired
private ApplicationEventPublisher publisher;

// 事件广播
@Autowired
private ApplicationEventMulticaster multicaster;

ApplicationContext

我们看到 ApplicationContext 继承ApplicationEventPublisher,那么这两个是不是就是一个对象呢。

实战部分

我们定义了一个controller,当用户注册的时候,我们需要像用户发送邮件,送新手礼物。

java 复制代码
   @GetMapping("regiest")
    public String regiest(@RequestParam("userName") String userName) {
         publisher.publishEvent(new RegisterEvent(userName));

        //context.publishEvent(new RegisterEvent(userName));

        //multicaster.multicastEvent(new RegisterEvent(userName));
        return "success";
    }
定义用户注册的事件
java 复制代码
public class RegisterEvent extends ApplicationEvent {



    public RegisterEvent(Object user) {
        super(user);
    }

    public String getUserName() {
        return getSource().toString();
    }
}
监听用户注册成功事件
  • 发放礼包

    java 复制代码
    @Service
    public class GiftService {
    
    
        @EventListener
        public void registerEvent(RegisterEvent event) {
            String userName = event.getUserName();
            System.out.println("发放新手礼品给: " + userName);
        }
    }
  • 邮件通知

    java 复制代码
    @Service
    public class EmailService {
    
    
        @EventListener
        public void registerEvent(RegisterEvent event) {
            String userName = event.getUserName();
            System.out.println("给用户: " + userName + "发送邮件");
        }
    }
启动类测试

源码分析部分

  • org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
    • org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
      • org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
        • org.springframework.context.event.ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
相关推荐
安娜的信息安全说2 小时前
工业与信息安全的交汇点:IT 与 OT 安全融合
网络·安全·web安全
Miqiuha2 小时前
设计模式之策略模式学习
学习·设计模式·策略模式
init_23613 小时前
路由策略和流量策略的常见配置
运维·服务器·网络
安当加密4 小时前
如何利用开源库和安全芯片设计fido令牌
网络·安全·开源
YC运维4 小时前
Nginx核心配置详解:访问控制、用户认证与HTTPS部署
网络·nginx·https
大飞pkz4 小时前
【设计模式】观察者模式
开发语言·观察者模式·设计模式·c#
apple_ttt6 小时前
融合:迈向 “一台计算机” 的终极架构
网络·架构·cxl·数据中心网络
祁弋6 小时前
UDP的理解
网络·网络协议·udp
青草地溪水旁7 小时前
设计模式(C++)详解——备忘录模式(2)
c++·设计模式·备忘录模式