【设计模式】一文速通观察者模式

125 阅读5分钟

1. 引入

假设此时我们有一个在线商店,用户想要买某种产品,可是ta想要购买的这种产品现在没货,用户怎样才能成功购买想要的产品呢?

针对上述这个功能,在这里介绍两种实现方法。

1.1 方案一:轮询

轮询这种方式,放在场景中可以理解为:用户不断上线查看ta关注的产品是否有货。

1.1.1 实现代码

  • 在线商店Store
// 在线商店
public class Store {
    // 商品库存
    private Map<String, Boolean> productStock = new HashMap<>();
    // 设置库存状态
    public void setStockStatus(String product, boolean inStock) {
        productStock.put(product, inStock);
    }
    // 查看库存状态
    public boolean checkStockStatus(String product) {
        return productStock.getOrDefault(product, false);
    }
}
  • 用户Customer
public class Customer {
    // 商店
    private Store store;
    // 产品
    private String product;

    public Customer(Store store, String product) {
        this.store = store;
        this.product = product;
    }
    // 检查库存
    public void checkStock() {
        boolean inStock = store.checkStockStatus(product);
        if (inStock) {
            System.out.println("产品 '" + product + "' 有货!");
        } else {
            System.out.println("产品'" + product + "' 没货.");
        }
    }
}

  • 客户端使用代码
public class Main {
    public static void main(String[] args) {
        Store store = new Store();
        Customer customer1 = new Customer(store, "Laptop");

        store.setStockStatus("Laptop", false);
        customer1.checkStock(); // Checks the stock status

        store.setStockStatus("Laptop", true);
        customer1.checkStock(); // Checks the stock status
    }
}

1.1.2 存在问题

  • 效率低:轮询需要周期性检查库存状态,会导致资源的浪费;

  • 高负载:多个客户轮询时,服务器会接收到大量请求,可能会导致性能瓶颈和系统负载增加;

  • 延迟响应:如果轮询间隔设置较长,客户需要等待较久的时间才能知道库存状态的变化。

1.2 方案二:采用观察者模式实现

1.2.1 实现代码

  • 定义观察者/订阅者接口
public interface Subscriber {
    void update(String product, boolean inStock);
}
  • 定义被观察者/发布者(在线商店)
// 在线商店
public class Store {
    private List<Subscriber> subscribers = new ArrayList<>();
    private String product;
    private boolean inStock;

    // 增加订阅者
    public void addSubscriber(Subscriber subscriber) {
        subscribers.add(subscriber);
    }
    // 删除订阅者
    public void removeSubscriber(Subscriber subscriber) {
        subscribers.remove(subscriber);
    }
    
    // 设置库存状态
    public void setStockStatus(String product, boolean inStock) {
        this.product = product;
        this.inStock = inStock;
        // 库存状态过更新时通知订阅用户
        notifySubscribers();
    }
    // 通知订阅者
    private void notifySubscribers() {
        for (Subscriber subscriber : subscribers) {
            subscriber.update(product, inStock);
        }
    }
}

  • 定义具体观察者/订阅者(用户)
// 具体的订阅者,实现Subscriber接口
public class Customer implements Subscriber {
    private String name;

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

    @Override
    public void update(String product, boolean inStock) {
        if (inStock) {
            System.out.println(name + " 接收到了通知,产品: '" + product + "' 有货了!");
        } else {
            System.out.println(name + " 接收到了通知,产品: '" + product + "' 没货了.");
        }
    }
}

  • Client使用代码
public class Main {
    public static void main(String[] args) {
        // 发布者
        Store store = new Store();

        Customer customer1 = new Customer("Alice");
        Customer customer2 = new Customer("Bob");
        
        // 添加订阅者
        store.addSubscriber(customer1);
        store.addSubscriber(customer2);

        // 设置laptop产品的库存状态
        System.out.println("Setting product 'Laptop' status to out of stock...");
        store.setStockStatus("Laptop", false);

        // 设置laptop产品的库存状态
        System.out.println("Setting product 'Laptop' status to in stock...");
        store.setStockStatus("Laptop", true);
    }
}

image.png

1.2.2 上述方案优点

  • 支持实时更新提高响应性

  • 减少了资源的消耗,系统负载减轻

  • 扩展性好:观察者模式支持动态添加/移除观察者,可以灵活处理不同数量的客户或服务,不需要改变检查逻辑;

  • 解耦:通知机制和具体的观察者解耦,提高了灵活性和可维护性。

2. 观察者模式

2.1 定义

观察者模式(Observer Pattern)/ 发布订阅模式(Publish/subscribe:一种行为设计模式,允许定义一种订阅机制,在对象事件发生时通知多个“观察者”。

2.2 角色及结构

2.3.1 角色

  • Subject(被观察者):定义被观察者必须实现的职责,要求能够动态增加、取消观察者。

  • Observer(观察者):观察者接收到消息后,进行更新操作,对接收到的信息进行处理。

  • ConcreteSubject(具体的被观察者):定义被观察者自己的业务逻辑,定义对哪些事件进行通知。

  • ConcreteObserver(具体的观察者):每个观察者接收到消息后的处理反应是不一样的,各个观察者都有自己的处理逻辑。

2.3.2 结构

image.png

2.3 适用场景

  • 跨系统的消息交换场景,如:消息队列的处理机制;

  • 当应用中的一些对象必须观察其他对象时, 可使用该模式。但仅能在有限时间内或特定情况下使用;

  • 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的。

2.4 优缺点

2.4.1 优点

  • 符合开闭原则,无需修改被观察者代码就能引入新的观察者,观察者和被观察者之间是抽象耦合。

  • 可以利用观察者模式建立一套触发机制,形成触发链条,将单一职责的类串联成真实世界中复杂的逻辑关系。

2.4.2 缺点

  • 效率问题:一个被观察者,多个观察者,开发和调试会比较复杂;Java中消息的通知默认顺序执行,一个观察者卡壳,会影响整体的执行效率。

2.5 注意事项

使用观察者模式需要重点关注以下两个问题:

  • 广播链问题:一个观察者可以有双重身份,如果建立链,逻辑就比较复杂,维护性差。因此,一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是消息最多转发一次。

  • 异步处理问题:如果被观察者执行了相关操作,观察者需要做出响应,如果观察者比较多,处理时间比较长,所以考虑使用异步,使用异步就需要考虑线程安全及队列的问题。

参考资料