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);
}
}
1.2.2 上述方案优点
-
支持实时更新,提高响应性;
-
减少了资源的消耗,系统负载减轻;
-
扩展性好:观察者模式支持动态添加/移除观察者,可以灵活处理不同数量的客户或服务,不需要改变检查逻辑;
-
解耦:通知机制和具体的观察者解耦,提高了灵活性和可维护性。
2. 观察者模式
2.1 定义
观察者模式(Observer Pattern
)/ 发布订阅模式(Publish/subscribe
):一种行为设计模式,允许定义一种订阅机制,在对象事件发生时通知多个“观察者”。
2.2 角色及结构
2.3.1 角色
-
Subject
(被观察者):定义被观察者必须实现的职责,要求能够动态增加、取消观察者。 -
Observer
(观察者):观察者接收到消息后,进行更新操作,对接收到的信息进行处理。 -
ConcreteSubject
(具体的被观察者):定义被观察者自己的业务逻辑,定义对哪些事件进行通知。 -
ConcreteObserver
(具体的观察者):每个观察者接收到消息后的处理反应是不一样的,各个观察者都有自己的处理逻辑。
2.3.2 结构
2.3 适用场景
-
跨系统的消息交换场景,如:消息队列的处理机制;
-
当应用中的一些对象必须观察其他对象时, 可使用该模式。但仅能在有限时间内或特定情况下使用;
-
当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的。
2.4 优缺点
2.4.1 优点
-
符合开闭原则,无需修改被观察者代码就能引入新的观察者,观察者和被观察者之间是抽象耦合。
-
可以利用观察者模式建立一套触发机制,形成触发链条,将单一职责的类串联成真实世界中复杂的逻辑关系。
2.4.2 缺点
- 效率问题:一个被观察者,多个观察者,开发和调试会比较复杂;
Java
中消息的通知默认顺序执行,一个观察者卡壳,会影响整体的执行效率。
2.5 注意事项
使用观察者模式需要重点关注以下两个问题:
广播链问题:一个观察者可以有双重身份,如果建立链,逻辑就比较复杂,维护性差。因此,一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是消息最多转发一次。
异步处理问题:如果被观察者执行了相关操作,观察者需要做出响应,如果观察者比较多,处理时间比较长,所以考虑使用异步,使用异步就需要考虑线程安全及队列的问题。
参考资料
- 《设计模式之禅》
- refactoringguru.cn/design-patt…