跳至主要內容

观察者模式(上):理解观察者模式

AruNi_Lu设计模式设计模式与范式约 2302 字大约 8 分钟

本文内容

前言

前面我们学习完了创建型和结构型的设计模式,从这篇文章开始,将学习最后一个类型的设计模式—行为型

创建型主要解决的是 “对象的创建” 问题,结构型主要解决的是 “类或对象的组合组装” 问题,而行为型主要解决的是 “类或对象之间的交互” 问题

行为型设计模式比较多,有:观察者模式、模版模式、策略模式、责任链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

观察者模式在实际开发中也比较常用,根据应用场景的不同有着不同的实现方式,常见的有如下几种:

  • 同步阻塞的实现
  • 异步非阻塞的实现
  • 进程内的实现
  • 跨进程的实现

这篇文章首先介绍观察者模式的原理、应用场景,以及它的几种不同实现方式。下一篇文章将基于观察者模式实现一个异步非阻塞的 EventBus,将观察者模式运用到实际场景中。

1. 观察者模式原理

观察者模式(Observer Design Pattern)也叫 发布订阅模式(Publish-Subscribe Design Pattern),官方定义如下:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

即:在对象之间定义一个一对多的依赖,当一个对象的状态改变时,所有依赖的对象都会自动收到通知(进而执行某种操作)。

一般被依赖的对象叫作 被观察者(Observable),依赖的对象叫作 观察者(Observer)。不过在实际开发时有很多种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener 等。

观察者模式有多种实现方式,一种典型的实现方式如下:

// 观察者
interface Observer {
    // 观察者—更新 Message 操作
    void update(Message message);
}

// 被观察者
interface Subject {
    // 注册/移除观察者
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    // 通知所有的观察者
    void notifyObservers(Message message);
}

// 具体的被观察者
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(Message message) {
        // 通知所有的观察者 update message
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// 具体的观察者 01
class ConcreteObserver01 implements Observer {
    @Override
    public void update(Message message) {
        // TODO: 获取消息通知,执行 ConcreteObserver01 自己的逻辑
        System.out.println("ConcreteObserver01 is notified.");
    }
}

// 具体的观察者 02
class ConcreteObserver02 implements Observer {
    @Override
    public void update(Message message) {
        // TODO: 获取消息通知,执行 ConcreteObserver02 自己的逻辑
        System.out.println("ConcreteObserver02 is notified.");
    }
}

public class ClassicObserver {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        subject.registerObserver(new ConcreteObserver01());
        subject.registerObserver(new ConcreteObserver02());
        // TODO: 发生某种事情,通知所有的观察者
        subject.notifyObservers(new Message());
    }
}

class Message {
}

解析:

  • Subject 是被观察者,定义一组注册/移除/通知观察者的方法,由具体的被观察者 ConcreteSubject 去实现。

  • Observer 是观察者,定义一组动作,所有具体的观察者(ConcreteObserver01 和 ConcreteObserver02)都继承自 Observer,实现这组动作。

  • 在 Observer 收到 Subject 状态变化的通知时(notifyObservers() 被调用),调用 Observer 具体实现类的这组动作,因为 Observer 的实现类各不相同,所有会做出各自不同的动作。

这是观察者模式的 “模板代码”,只能反映出大致的设计思路。在实际应用时,都是比较灵活的,不必一五一十地照着实现。

2. 应用场景

观察者模式原理非常简单,重点来看看具体的应用场景,在什么情况下需要使用观察者模式,观察者模式能解决什么问题?

假设一个投资理财系统在用户注册后会发放投资体验金,那么这个系统的 UserController 大致实行如下:

public class UserController {
  private UserService userService; // 依赖注入
  private PromotionService promotionService; // 依赖注入

  public Long register(String telephone, String password) {
    // 省略输入参数的校验代码
    // 省略 userService.register() 异常的 try-catch 代码
    long userId = userService.register(telephone, password);
    promotionService.issueNewUserExperienceCash(userId);	// 发放体验金
    return userId;
  }
}

这里虽然 注册接口做了两件事—注册和发放体验金,违反了单一职责原则,但是如果 后续没有扩展和修改的需求,这样的实现是可以接受的。如果 非要使用观察者模式,就意味着要引入更多的类和代码,反而是一种 过度设计

相反,如果 需求频繁变动,比如用户注册后,又要改为发放优惠卷、还要给用户发送一封 “欢迎注册” 的站内信。此时如果还用上面的设计,就需要 频繁改动 register() 中的代码,违反了开闭原则。而且将多个功能写在同一个接口下会 增加该接口的复杂度,影响代码的可读性和可维护性。

此时,观察者模式就派上用场了。我们利用观察者模式进行重构:

// 监听器(观察者)
interface RegisterListener {
    void handleRegisterSuccess(long userId);
}

class RegisterPromotionListener implements RegisterListener {
    private PromotionService promotionService;

    @Override
    public void handleRegisterSuccess(long userId) {
        promotionService.issueNewUserExperienceCash(userId);
    }
}

class RegisterNotificationListener implements RegisterListener {
    private NotificationService notificationService;

    @Override
    public void handleRegisterSuccess(long userId) {
        notificationService.sendInboxMessage(userId, "Welcome...");
    }
}

// 触发器(被监听者)
class RegisterDispatcher {
    private List<RegisterListener> listeners = new ArrayList<>();

    public void addListener(RegisterListener listener) {
        listeners.add(listener);
    }

    public void removeListener(RegisterListener listener) {
        listeners.remove(listener);
    }

    public void notifyListeners(long userId) {
        for (RegisterListener listener : listeners) {
            listener.handleRegisterSuccess(userId);
        }
    }
}

class UserController {
    private UserService userService;

    private RegisterDispatcher registerDispatcher;

    public Long register(String telephone, String password) {
        //省略输入参数的校验代码
        // 省略 userService.register() 异常的 try-catch 代码
        long userId = userService.register(telephone, password);

        // 添加需要的监听器
        addListenerOfRegister();
        // 通知所有监听器
        registerDispatcher.notifyListeners(userId);

        return userId;
    }

    // 添加 Register 相关的 Listener,实际场景可以把 List 作为参数,由外部调用者决定要注入什么 Listener
    private void addListenerOfRegister() {
        registerDispatcher.addListener(new RegisterPromotionListener());
        registerDispatcher.addListener(new RegisterNotificationListener());
    }
}

当需要添加监听器时,现在就只需要添加一个实现了 RegisterListener 的类,然后在 addListenerOfRegister() 方法中添加该类即可。

其实设计模式主要作用就是解耦:

  • 创建型将对象的创建和使用解耦
  • 结构型将不同行为(功能代码)解耦
  • 观察者模式将观察者和被观察者的代码解耦

3. 观察者模式的不同实现方式

观察者模式的应用非常广泛,比如我们常用的订阅系统,如邮件订阅、RSS Feeds 等,本质都是观察者模式。

不同的应用场景下,观察者模式有着不同的实现,在文章开头也说过,主要有以下几种实现方式:

  • 同步阻塞的实现
  • 异步非阻塞的实现
  • 进程内的实现
  • 跨进程的实现

同步阻塞 的实现方式,观察者和被观察者代码在同一个线程内执行,被观察者要一直阻塞,直到所有观察者的方法都执行完

上面投资理财的例子,就是一种 同步阻塞 的实现方式,register() 中需要等每个观察者的 handleRegSuccess() 函数都执行完成之后,才会返回结果给客户端。

了解了同步阻塞后,异步非阻塞 的实现方式就很简单了,只需要 启动一个新的线程来执行观察者的方法 即可。

上面的两种实现方式,其实都属于进程内的实现,那如何实现一个 跨进程的观察者模式 呢?在微服务架构中,常见的做法是使用 RPC 接口 来进行处理,不过一般我们会使用更优雅的实现方式—MQ。基于 MQ,被观察者和观察者解耦将更加彻底,被观察者和观察者两两都是无感知的。被观察者只管发送消息到 MQ,观察者只管从 MQ 中消费消息(执行对应的逻辑)。

观察者模式和生产者-消费者模型的联系和区别:

联系

  • 生产者-消费者模型可以算观察者模式的一种异步非阻塞的应用

区别

  • 在观察者模式中,被观察者知道观察者的存在,要主动调用方法通知它;而在生产者-消费者模型中,由于加入了中间层队列,所以生产者和消费者完全对对方无感知,实现了完全解耦
  • 在观察者模式中,被观察者发送了通知,其他观察者都会对此通知进行处理;而在生产者-消费者模型中,一条消息一般只会有一个消费者消费
上次编辑于: