【 设计模式 | 行为型模式 观察者模式 】

摘要:本文介绍设计模式中的观察者模式,核心是通过抽象主题、具体主题、抽象观察者、具体观察者四大角色,解决**"一对多"依赖关系,实现主题状态变时自动通知订阅观察者、观察者同步更新的逻辑**,达成两者解耦与状态变化自动化响应。

思维导图

1. 观察者模式

1.1 概述

观察者模式(又称发布 - 订阅模式)是一种解决**"一对多"依赖关系的设计模式,它通过 抽象主题、具体主题、抽象观察者、具体观察者 这四个角色,让多个观察者对象可订阅** 某一主题对象;当主题对象状态改变时,会自动通知所有已订阅 的观察者,使观察者无需主动查询就能同步更新自身状态,最终实现主题与观察者的解耦及状态变化的自动化响应。

1.2 结构

观察者模式包含四个核心角色,分工明确且职责递进:

  1. 抽象主题(Subject) :作为被观察者的抽象定义,负责通过集合管理所有观察者,提供addObserver()(添加)、removeObserver()(移除)、notifyObservers()(通知)三个必选接口,规范观察者的管理与通知机制。

  2. 具体主题(ConcreteSubject)实现抽象主题接口 ,存储自身具体状态;当状态发生改变时,通过调用notifyObservers()向所有注册的观察者发送通知

  3. 抽象观察者(Observer):作为观察者的抽象定义,仅声明一个**update()更新接口**,规定观察者接收被观察者状态变化通知后需执行更新操作。

  4. 具体观察者(ConcreteObserver)实现抽象观察者的update()接口,在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态。

这四个角色通过 "抽象定义规则、具体落地实现" 的方式,构建起 "主题状态变化→自动通知→观察者响应更新" 的完整逻辑闭环。

1.3 案例实现

**例】微信公众号:**在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

代码如下:

定义抽象观察者类,里面定义一个更新的方法

java 复制代码
//仅声明一个update()更新接口,规定观察者接收被观察者状态变化通知后需执行更新操作
public interface Observer {
    void update(String message);
}

定义具体观察者类,微信用户是观察者,里面实现了更新的方法

java 复制代码
//实现抽象观察者的update()接口
public class WeixinUser implements Observer {
    // 微信用户名
    private String name;
​
    public WeixinUser(String name) {
        this.name = name;
    }

    //在方法中定义自身接收通知后的具体反应逻辑,以同步更新自身状态
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }
}

定义抽象主题类,提供了attach、detach、notify三个方法

java 复制代码
//提供attach()(添加)、 detach()(移除)、notify()(通知)三个必选接口
public interface Subject {
    //增加订阅者
    public void attach(Observer observer);
​
    //删除订阅者
    public void detach(Observer observer);
    
    //通知订阅者更新消息
    public void notify(String message);
}

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法

java 复制代码
public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();
​
    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }
​
    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }
​
    //当状态发生改变时,通过调用notifyObservers()向所有注册的观察者发送通知
    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

客户端程序

java 复制代码
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //创建微信用户
        WeixinUser user1=new WeixinUser("孙悟空");
        WeixinUser user2=new WeixinUser("猪悟能");
        WeixinUser user3=new WeixinUser("沙悟净");
        //订阅公众号
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公众号更新发出消息给订阅的微信用户
        mSubscriptionSubject.notify("传智黑马的专栏更新了");
    }
}
​

1.4 代码详解1

这段代码是观察者模式的经典实现,之所以可能 "看不出来",是因为它用 "微信公众号订阅" 这个贴近生活的场景,隐藏了设计模式的 "抽象逻辑"。我们通过拆解它解决的核心问题、对比 "不用观察者模式的弊端",就能清晰看到它的作用:

一、先明确观察者模式的核心角色(对应代码)

角色 代码中的实现类 职责描述
被观察者 SubscriptionSubject(公众号) 维护订阅者列表,当自身有更新时,通知所有订阅者
观察者 WeixinUser(微信用户) 订阅被观察者,接收并处理被观察者的通知
抽象接口 SubjectObserver接口 定义规范:被观察者要能 "增删订阅者、发通知",观察者要能 "接收通知"

二、观察者模式的核心作用:解耦 "发布者" 和 "订阅者"

1. 没有观察者模式时:公众号如何通知用户?

如果不用观察者模式,公众号要通知用户,必须直接调用每个用户的 "接收消息" 方法。

java 复制代码
// 无观察者模式的公众号
public class BadSubscriptionSubject {
    // 公众号要直接持有所有用户的引用
    private WeixinUser user1;
    private WeixinUser user2;
    private WeixinUser user3;
​
    // 新增用户时,必须修改公众号代码,加新的引用
    public void addUser(WeixinUser user) {
        if (user.getName().equals("孙悟空")) user1 = user;
        else if (user.getName().equals("猪悟能")) user2 = user;
        // ... 新增用户就要加判断,极其繁琐
    }
​
    // 发通知时,必须逐个调用用户的方法,漏一个都不行
    public void sendMessage(String message) {
        user1.update(message);
        user2.update(message);
        user3.update(message);
        // 新增用户后,这里必须手动加一行 user4.update(message),否则新用户收不到
    }
}

弊端一目了然

  • 公众号和用户强耦合:公众号要知道所有用户的存在,还要手动管理每个用户的调用;

  • 扩展困难:新增用户,必须修改公众号的addUsersendMessage方法,违反 "开闭原则"

  • 维护成本高:用户取消订阅时,要手动从代码中删除调用,容易漏删导致报错。


2. 有观察者模式时:代码如何解决这些问题?

1. 用 "列表" 代替 "单个引用" :公众号用List<Observer> weixinUserlist存储用户,不管有多少用户,都统一存在列表里,不用手动加user1、user2的引用。

2. 用 "接口" 代替 "具体类" :公众号依赖的是Observer接口,不是WeixinUser具体类 ------ 如果未来新增 "抖音用户"(实现Observer接口),公众号无需修改,直接加入列表即可接收通知(扩展性极强)。

3. 通知逻辑 "自动化" :发通知时,公众号只需遍历列表调用observer.update(message),不用关心列表里有多少用户、是谁新增 / 删除用户,只需调用attach/detach操作列表,notify方法完全不用改。

1.4 优缺点

优点:

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。

  2. 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

缺点:

  1. 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时

  2. 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

1.6 JDK提供

Java 中通过**java.util.Observable 类(抽象被观察者)**和 **java.util.Observer 接口(抽象观察者)**内置实现了观察者模式,核心机制如下:

Observable 类:作为被观察者的抽象实现,内部提供三个核心方法:

  • addObserver(Observer o):将观察者添加到集合中;
  • setChange() :设置内部标志为 true,标记被观察者状态已变化;
  • notifyObservers(Object arg) :当 setChange() 标记为**true** 时,触发集合中所有观察者的 update 方法**(晚加入的观察者优先收到通知)**,传递变化信息。

Observer 接口 :定义唯一方法**update()** ,当被观察者调用 notifyObservers() 时,观察者通过该方法接收通知并处理(o 为被观察者实例,arg 为传递的变化数据)。

只需基于这两个类 / 接口实现子类,即可快速构建观察者模式实例。

例】警察抓小偷:警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。

小偷是一个被观察者,所以需要继承Observable类

java 复制代码
public class Thief extends Observable {
​
    private String name;
​
    public Thief(String name) {
        this.name = name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void steal() {
        System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
        super.setChanged(); //changed  = true
        super.notifyObservers();
    }
}

警察是一个观察者,所以需要让其实现Observer接口

java 复制代码
public class Policemen implements Observer {
​
    private String name;
​
    public Policemen(String name) {
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
    }
}

客户端代码

java 复制代码
public class Client {
    public static void main(String[] args) {
        //创建小偷对象
        Thief t = new Thief("隔壁老王");
        //创建警察对象
        Policemen p = new Policemen("小李");
        //让警察盯着小偷
        t.addObserver(p);
        //小偷偷东西
        t.steal();
    }
}

1.7 代码详解2

这段代码基于 Java 内置的**Observable类(被观察者基类)Observer接口(观察者接口)**实现了观察者模式,其核心步骤可拆解为 "定义角色 → 绑定关系 → 触发通知" 三大环节,每个环节对应明确的编码操作,适用于所有基于 Java 内置 API 的观察者模式实现:

第一步:定义观察者模式的核心角色(2 个核心角色)

观察者模式必含 "被观察者" 和 "观察者",Java 已提供现成的抽象接口 / 基类,按规则实现即可:

1. 定义 "被观察者"(需继承Observable类)

被观察者是 "事件发起者"(如案例中的小偷,偷东西是触发事件的行为),需满足:

  • 继承 Java 提供的java.util.Observable类(该类已内置 "订阅列表管理" "通知触发" 的基础逻辑,无需手动写attach/detach

  • 在 "事件发生时",先调用**setChanged()标记 "状态已变化"**(Observable的要求:只有标记为变化,后续通知才会生效),再调用notifyObservers()触发通知(可传参数)

案例对应代码

java 复制代码
public class Thief extends Observable { // 继承内置被观察者基类
    // ... 其他属性和方法
    
    // 事件触发方法(小偷偷东西)
    public void steal() {
        System.out.println("小偷:我偷东西了...");
        super.setChanged(); // 标记状态变化(必须有这一步,否则通知不生效)
        super.notifyObservers(); // 触发通知,通知所有观察者
    }
}

2. 定义 "观察者"(需实现Observer接口)

在 Java 的观察者模式中,观察者作为 "事件接收者"(如警察接收 "小偷作案" 通知),需满足:

  1. 必须实现 Java 提供的java.util.Observer接口;

  2. 必须重写接口中的update(Observable o, Object arg)方法

  • 参数o:表示触发通知的被观察者实例,可强转为具体被观察者类以获取详细信息(如从 "小偷" 实例中获取姓名);
  • 参数arg:用于接收被观察者传递的额外数据(如作案地点等补充信息);
  • 方法体:编写观察者收到通知后的具体业务逻辑(如警察根据通知执行抓捕的话术或行动)。

案例对应代码

java 复制代码
public class Policemen implements Observer { // 实现内置观察者接口
    // ... 其他属性和方法
    
    // 重写通知响应方法
    @Override
    public void update(Observable o, Object arg) {
        // 强转被观察者为具体类,获取信息
        Thief thief = (Thief) o;
        // 观察者的业务逻辑(警察响应)
        System.out.println("警察:" + thief.getName() + ",我盯你很久了...");
    }
}

第二步:建立 "被观察者 - 观察者" 的订阅关系

被观察者需要知道 "哪些观察者要接收通知",需通过Observable类内置的addObserver(Observer o)方法,将观察者 "注册" 到被观察者的 "订阅列表" 中(取消订阅用deleteObserver(Observer o))。

这一步是 "解耦的关键":被观察者只需维护 "订阅列表",无需知道观察者的具体实现,新增观察者只需加一行注册代码,无需修改被观察者逻辑。

案例对应代码

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 1. 创建被观察者实例(小偷)
        Thief t = new Thief("隔壁老王");
        // 2. 创建观察者实例(警察)
        Policemen p = new Policemen("小李");
        // 3. 建立订阅关系:警察"盯"上小偷(注册到被观察者列表)
        t.addObserver(p); 
    }
}

第三步:触发事件,被观察者自动通知观察者

当被观察者发生 "目标事件"(如案例中小偷 "偷东西"),调用被观察者中定义的 "事件触发方法"(如steal()),该方法会通过setChanged()notifyObservers(),自动遍历 "订阅列表",调用每个观察者的update()方法,完成通知。

这一步是 "自动化通知的核心":被观察者无需手动调用每个观察者的方法,Observable基类已封装遍历逻辑,观察者只需专注于 "收到通知后做什么"。

案例对应代码

java 复制代码
public class Client {
    public static void main(String[] args) {
        // ... 前面的实例创建、订阅关系代码
        
        // 4. 触发事件:小偷偷东西,自动通知所有观察者(警察)
        t.steal(); 
    }
}

总结:观察者模式的通用实现步骤(基于 Java 内置 API)

  1. 定义被观察者 :继承Observable类,在事件方法中调用setChanged()+notifyObservers()

  2. 定义观察者 :实现Observer接口,重写update()方法写响应逻辑;

  3. 建立订阅关系 :调用被观察者的addObserver(),将观察者注册到被观察者列表;

  4. 触发通知:调用被观察者的事件方法,自动通知所有已注册的观察者。

核心优势: 利用 Java 内置 API 简化了 "订阅列表管理""通知遍历" 的重复代码,同时严格遵循观察者模式的 "解耦" 思想 ------ 被观察者和观察者仅通过Observable/Observer接口交互,互不依赖具体实现。


大功告成!

相关推荐
thginWalker2 小时前
Java 热门面试题200道之JVM(7 题)
java·jvm
fengdongnan2 小时前
JVM 类加载器详解
java·jvm·类加载器
安然~~~2 小时前
常见的【垃圾收集算法】
java·jvm
低调小一2 小时前
理解 JVM 的 8 个原子操作与 `volatile` 的语义
java·jvm
Familyism2 小时前
Java虚拟机——JVM
java·开发语言·jvm
^辞安2 小时前
什么是Mvcc
java·数据库·mysql
烈风3 小时前
009 Rust函数
java·开发语言·rust
静心观复3 小时前
观察者模式
观察者模式
这次选左边3 小时前
Flutter混合Android开发Release 打包失败GeneratedPluginRegistrant.java,Plugin不存在
android·java·flutter