基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

设计模式--观察者模式 (Observer)

知识点图片

观察者模式定义了一种一对多的依赖关系,当一个对象(主题)状态改变时,所有依赖它的对象(观察者)都会自动收到通知。核心是实现主题与观察者之间的解耦。

我们来详细、清晰地解析一下设计模式中的观察者模式(Observer Pattern)

1. 什么是观察者模式?

观察者模式是一种行为型设计模式(Behavioral Pattern)。

核心定义: 它定义了对象之间一种一对多的依赖关系。当一个对象(被称为“主题”或“被观察者”)的状态发生改变时,所有依赖于它的对象(被称为“观察者”)都会得到通知并自动更新。

通俗比喻:
想象一下你在B站或YouTube上关注(Subscribe)了一个UP主。

  • UP主 (Subject / 被观察者): 他是内容的发布者。
  • 你和其他粉丝 (Observers / 观察者): 你们是内容的接收者。

当UP主发布一个新视频时(状态改变),B站的系统会自动向所有关注他的粉丝发送通知(“你关注的UP主更新啦!”)。你不需要每天去刷新UP主的主页,系统会自动“推”送消息给你。

在这个过程中:

  • UP主不需要知道具体是“张三”还是“李四”关注了他,他只管发布视频。
  • 你也不需要直接和UP主联系,你只管关注和接收通知。
  • UP主和粉丝之间通过“关注”这个行为解耦了。

这就是观察者模式的核心思想:解耦主题与观察者


2. 为什么需要观察者模式?(它解决了什么问题)

如果没有观察者模式,当UP主发布新视频时,他可能需要一个“粉丝列表”,然后手动一个一个去通知他们。

plaintext
// 伪代码:紧耦合的方式
class UpUploader {
    FanA fanA;
    FanB fanB;
    FanC fanC;

    void publishNewVideo() {
        // ...发布视频的逻辑...
        // 紧密耦合:UpUploader 必须知道每一个具体的粉丝对象
        fanA.receiveUpdate("新视频来了!");
        fanB.receiveUpdate("新视频来了!");
        fanC.receiveUpdate("新视频来了!");
    }
}

这种方式的缺点非常明显:

  1. 紧密耦合: UpUploader 类依赖于具体的 FanA, FanB 等类。
  2. 难以扩展: 每当有新粉丝 FanD 加入,就必须修改 UpUploader 类的代码。这违反了开闭原则(对扩展开放,对修改关闭)。
  3. 职责不清: UpUploader 不仅要负责发布视频,还要负责通知粉丝,职责过重。

观察者模式通过引入一个抽象层,完美地解决了这些问题。


3. 观察者模式的结构与参与者

观察者模式通常包含以下四个核心角色:

  1. Subject (主题 / 抽象被观察者):

    • 定义了一个接口,用于附加(Attach)分离(Detach)通知(Notify)观察者。
    • 它维护了一个观察者对象的列表。
  2. ConcreteSubject (具体主题 / 具体被观察者):

    • 实现了 Subject 接口。
    • 它包含着可能会发生变化的状态。
    • 当其状态改变时,会向所有已注册的观察者发出通知。
  3. Observer (观察者 / 抽象观察者):

    • 定义了一个更新接口(通常是 update() 方法),当接收到主题的通知时,该方法被调用。
  4. ConcreteObserver (具体观察者):

    • 实现了 Observer 接口。
    • 它维护着一个指向 ConcreteSubject 对象的引用。
    • update() 方法中实现具体的业务逻辑,以响应主题状态的变化。

UML 类图

Observer Pattern UML


4. 代码实现示例 (Java)

让我们用代码来实现上面那个“UP主与粉丝”的例子。

第1步:定义抽象接口

java
// Observer (抽象观察者)
interface Observer {
    void update(String message);
}

// Subject (抽象主题)
interface Subject {
    void attach(Observer observer); // 附加观察者(关注)
    void detach(Observer observer); // 分离观察者(取关)
    void notifyObservers();        // 通知所有观察者
}

第2步:创建具体实现类

java
import java.util.ArrayList;
import java.util.List;

// ConcreteSubject (具体主题 - UP主)
class UpUploader implements Subject {
    private List<Observer> fans = new ArrayList<>();
    private String latestVideoTitle;

    @Override
    public void attach(Observer observer) {
        fans.add(observer);
        System.out.println(observer + " 关注了UP主!");
    }

    @Override
    public void detach(Observer observer) {
        fans.remove(observer);
        System.out.println(observer + " 取消了关注!");
    }

    @Override
    public void notifyObservers() {
        // 遍历粉丝列表,通知每一个粉丝
        for (Observer fan : fans) {
            fan.update(latestVideoTitle);
        }
    }

    // UP主发布新视频
    public void publishNewVideo(String videoTitle) {
        this.latestVideoTitle = videoTitle;
        System.out.println("\nUP主发布了新视频: 《" + videoTitle + "》");
        // 视频发布后,通知所有粉丝
        notifyObservers();
    }
}

// ConcreteObserver (具体观察者 - 粉丝)
class Fan implements Observer {
    private String name;

    public Fan(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " 收到了通知: " + "UP主的新视频是《" + message + "》, 马上就去看!");
    }

    @Override
    public String toString() {
        return "粉丝[" + name + "]";
    }
}

第3步:客户端测试

java
public class Demo {
    public static void main(String[] args) {
        // 1. 创建一个UP主 (具体主题)
        UpUploader up = new UpUploader();

        // 2. 创建几个粉丝 (具体观察者)
        Observer fanA = new Fan("张三");
        Observer fanB = new Fan("李四");
        Observer fanC = new Fan("王五");

        // 3. 粉丝关注UP主 (将观察者附加到主题)
        up.attach(fanA);
        up.attach(fanB);
        up.attach(fanC);

        // 4. UP主发布新视频 (主题状态改变,并通知观察者)
        up.publishNewVideo("观察者模式的详细教学");

        // 5. 有一个粉丝取关了
        System.out.println("\n--- 李四觉得视频太难,取关了 ---");
        up.detach(fanB);
        
        // 6. UP主又发布了一个新视频
        up.publishNewVideo("单例模式其实很简单");
    }
}

运行结果:

plaintext
粉丝[张三] 关注了UP主!
粉丝[李四] 关注了UP主!
粉丝[王五] 关注了UP主!

UP主发布了新视频: 《观察者模式的详细教学》
张三 收到了通知: UP主的新视频是《观察者模式的详细教学》, 马上就去看!
李四 收到了通知: UP主的新视频是《观察者模式的详细教学》, 马上就去看!
王五 收到了通知: UP主的新视频是《观察者模式的详细教学》, 马上就去看!

--- 李四觉得视频太难,取关了 ---
粉丝[李四] 取消了关注!

UP主发布了新视频: 《单例模式其实很简单》
张三 收到了通知: UP主的新视频是《单例模式其实很简单》, 马上就去看!
王五 收到了通知: UP主的新视频是《单例模式其实很简单》, 马上就去看!

从结果可以看出,当UP主发布新视频时,所有关注他的粉丝都收到了通知。当李四取关后,他就再也收不到新视频的通知了。整个过程,UpUploader 类完全不知道 Fan 类的存在,它只与 Observer 接口交互,实现了松耦合。


5. 优缺点

优点:

  1. 解耦: 完美地将主题和观察者解耦。主题只知道它有一系列观察者,但不知道它们是谁,是什么类型。
  2. 符合开闭原则: 可以随时增加新的观察者,而无需修改主题的代码。
  3. 广播机制: 一个主题的改变可以通知到任意数量的观察者,非常适合实现广播式的通信。
  4. 职责分离: 主题只负责维护自身状态和管理观察者列表,而观察者则负责在收到通知后执行自己的逻辑,职责清晰。

缺点:

  1. 通知顺序不确定: 如果观察者之间的更新顺序很重要,那么观察者模式默认的实现可能无法满足,需要额外逻辑来控制。
  2. 可能导致级联更新: 如果一个观察者的 update 方法又会触发其他对象的状态改变,可能会形成一个复杂的更新链条,导致系统难以调试和维护(雪崩效应)。
  3. 内存泄漏风险(“Lapsed listener problem”): 如果一个观察者被销毁了,但没有从主题中分离(detach),主题仍然会持有对这个观察者的引用,导致它无法被垃圾回收,从而造成内存泄漏。

6. 实际应用场景

观察者模式在现实世界的软件开发中非常常见:

  • GUI事件处理: 比如按钮(Subject)的点击事件。多个监听器(Observers)可以注册到按钮上,当按钮被点击时,所有监听器都会收到通知并执行相应的操作。例如 Java AWT/Swing 的 ActionListener,JavaScript 的 addEventListener
  • MVC/MVVM 架构:
    • 在MVC中,模型(Model)是主题,视图(View)是观察者。当模型的数据发生变化时,它会通知所有相关的视图进行更新。
  • 消息队列/事件总线: 发布-订阅模式(Publish-Subscribe Pattern)是观察者模式的一个变体。生产者(Publisher)发布消息到队列(Topic/Channel),所有订阅了该主题的消费者(Subscribers)都会收到消息。
  • 电子表格: 在Excel这样的应用中,一个单元格(Subject)的值发生改变,所有依赖于它的公式的单元格(Observers)都需要重新计算和更新。

7. 与发布-订阅模式的区别

虽然两者非常相似,但有一个关键区别:

  • 观察者模式: 主题(Subject)直接持有观察者(Observer)的引用,并直接调用其 update 方法。它们之间是直接通信的。
  • 发布-订阅模式: 发布者(Publisher)和订阅者(Subscriber)之间是完全解耦的,它们通过一个中间件(如消息代理、事件总线)进行通信。发布者将消息发送到中间件,订阅者从中间件接收消息。彼此完全不知道对方的存在。

可以认为,发布-订阅模式是观察者模式的“升级版”,解耦更彻底。

希望这个详细的解释能帮助你完全理解观察者模式!

00:00
00:00