观察者模式
● 行为设计模式,允许对象存在的一种一对多的关系
● 当一个对象发生变化,所有依赖他的对象都会得到通知并自动更新。
● 在这种模式中,发生状态变化的对象叫作“主题”,依赖他的对象被称为“观察者”
观察者例子的简单实践
假设我们有一个气象站 (WeatherStation),需要向许多不同的显示设备(如手机App、网站、电子屏幕 等)提供实时天气数据。
- 创建主题接口
package com.hillky.desgin_learn.observer;public interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}
- 创建一个Observer接口,表示观察者:
package com.hillky.desgin_learn.observer;public interface Observer {void update(float temperature, float humidity, float pressure);
}
- 创建一个具体的主题,如WeatherStation,实现Subject接口:
package com.hillky.desgin_learn.observer;import java.util.ArrayList;public class WeatherStation implements Subject{private ArrayList<Observer> observers;// 温度private float temperature;public WeatherStation() {observers=new ArrayList<>();}public void setTemperature(float temperature) {this.temperature = temperature;// 通知观察者notifyObservers();}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(temperature);}}
}
- 创建具体的观察者,如PhoneApp,,WebApp,实现Observer接口:
package com.hillky.desgin_learn.observer;public class WebApp implements Observer{@Overridepublic void update(float temperature) {System.out.println("WebApp修改了温度===>"+temperature);}}package com.hillky.desgin_learn.observer;public class PhoneApp implements Observer{@Overridepublic void update(float temperature) {System.out.println("PhoneApp修改了温度===>"+temperature);}}
- 创建一个WeatherStation实例并向其注册PhoneApp观察者。当 WeatherStation的数据发生变化时,PhoneApp会收到通知并更新自己的显示
package com.hillky.desgin_learn.observer;public class Client {public static void main(String[] args) throws InterruptedException {//创建主题WeatherStation weatherStation=new WeatherStation();//创建观察者Observer phoneApp = new PhoneApp();Observer webApp = new WebApp();//主题绑定观察者weatherStation.registerObserver(phoneApp);weatherStation.registerObserver(webApp);//气象站更新weatherStation.setTemperature(26.5F);Thread.sleep(5000);weatherStation.setTemperature(32.5F);}
}
● 观察者和主题者之间互相解耦
● 可以动态增加或删除观察者
● 主题和观察者之间的通信是自动的(主题更新,观察者自动更新)
使用场景
● 股票行情应用(股票价格发生变化,通知订阅的投资者)
● 网络聊天室
● 订阅系统
电商系统观察者模式实践
假设我们有一个电商系统,当某件商品有促销活动时,需要通知所有订阅了该商品的 用户。在这个例子中,商品是主题,用户是观察者,其代码逻辑和第一节的例子不能 说完全一样,也基本是一模一样
- 定义一个Subject接口,表示主题:
package com.hillky.desgin_learn.observer.mall;public interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}
- 创建一个Observer接口,表示观察者
package com.hillky.desgin_learn.observer.mall;public interface Observer {void update(String discountInfo);
}
- 创建一个具体的主题,如Product,实现Subject接口
package com.hillky.desgin_learn.observer.mall;import java.util.ArrayList;public class Product implements Subject{private ArrayList<Observer> observers;// 折扣消息private String discountInfo;public Product() {observers=new ArrayList<>();}public void discountChanged() {notifyObservers();}public void setDiscountInfo(String discountInfo) {this.discountInfo = discountInfo;discountChanged();}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(discountInfo);}}
}
- 创建一个具体的观察者,如User,实现Observer接口:
package com.hillky.desgin_learn.observer.mall;public class User implements Observer{private String userName;private String discountInfo;private Subject product;public User(String userName, Subject product) {this.userName = userName;this.product = product;}public void display() {System.out.println("用户 " + userName + " 收到促销通知: " + discountInfo);}@Overridepublic void update(String discountInfo) {this.discountInfo=discountInfo;display();}
}
- 以创建一个Product实例并向其注册User观察者。当Product的促销信息 发生变化时,User会收到通知并显示促销信息。
package com.hillky.desgin_learn.observer.mall;public class Client {public static void main(String[] args) {Product product = new Product();User user1 = new User("张三", product);User user2 = new User("李四", product);// 模拟商品促销信息更新product.setDiscountInfo("本周末满100减50");product.setDiscountInfo("双十一全场5折");}
}
发布订阅
● 发布-订阅模式和观察者模式都是用于实现对象间的松耦合通信的设计模式
● 可以将发布-订阅模式看作是 观察者模式的一种变体或扩展。
区别
● 观察者模式定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化 时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
● 发布-订阅模式(生产者和消费者)与观察者模式类似,但它们之间有一个关键区 别:发布-订阅模式引入了一个第三方组件(通常称为消息代理或事件总线),该组 件负责维护发布者和订阅者之间的关系。
实践
- 订阅者接口定义
package com.hillky.desgin_learn.observer.release;public interface Subscriber {void onEvent(String event);
}
- 实现一个具体的订阅者
package com.hillky.desgin_learn.observer.release;public class ConcreteSubscriber implements Subscriber{@Overridepublic void onEvent(String event) {System.out.println("收到事件: " + event);}
}
- 创建消息总线类(消息的中间件)
package com.hillky.desgin_learn.observer.release;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class EventBus {// 使用一个map维护,消息类型和该消息的订阅者private Map<String, List<Subscriber>> subscribers = new HashMap<>();public void subscribe(String eventType, Subscriber subscriber) {subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(subscriber);}public void unsubscribe(String eventType, Subscriber subscriber) {List<Subscriber> subs = subscribers.get(eventType);if (subs != null) {subs.remove(subscriber);}}public void publish(String eventType, String event) {List<Subscriber> subs = subscribers.get(eventType);if (subs != null) {for (Subscriber subscriber : subs) {subscriber.onEvent(event);}}}
}
- 测试使用
package com.hillky.desgin_learn.observer.release;public class Client {public static void main(String[] args) {EventBus eventBus = new EventBus();Subscriber subscriber1 = new ConcreteSubscriber();Subscriber subscriber2 = new ConcreteSubscriber();// 订阅事件eventBus.subscribe("eventA", subscriber1);eventBus.subscribe("eventA", subscriber2);// 发布事件eventBus.publish("eventA", "这是事件A的内容");// 取消订阅eventBus.unsubscribe("eventA", subscriber1);// 再次发布事件eventBus.publish("eventA", "这是事件A的新内容");}
}
源码使用(结合Gpt找例子)
JDK中的使用
● java.util.Observable类实现了主题(Subject)的功能,而java.util.Observer接口则 定义了观察者(Observer)的方法。
● 通过调用Observable对象的notifyObservers()方法,可以通知所有注册的Observer 对象,让它们更新自己的状态。
Guava中的消息总线
● Guava 库中的 EventBus 类提供了一个简单的消息总线实现,可以帮助您在 Java 应用程序中实现发布-订阅模式。
进阶
异步非阻塞模型
同步阻塞的实现方式。观察者和被观察者代码在同一个 线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。利用多线程替换遍历通知
实践
- 创建观察者接口
package com.hillky.desgin_learn.observer.sync;public interface Observer {void update(String message);
}
- 被观察者的接口
package com.hillky.desgin_learn.observer.sync;public interface Observable {void addObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}
- 实现一个具体的被观察者类 Subject
package com.hillky.desgin_learn.observer.sync;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Subject implements Observable{private List<Observer> observers;private ExecutorService executorService;public Subject() {observers=new ArrayList<>();executorService= Executors.newCachedThreadPool();}@Overridepublic void addObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (Observer observer : observers) {executorService.submit(() -> observer.update(message));}}public void setMessage(String message) {notifyObservers(message);}}
- 实现具体的观察者ConcreteObserver
package com.hillky.desgin_learn.observer.sync;public class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(String message) throws InterruptedException {Thread.sleep(5000);System.out.println(name + " received message: " + message);}
}
- 简单示例调用
package com.hillky.desgin_learn.observer.sync;public class Client {public static void main(String[] args) throws InterruptedException {Subject subject = new Subject();ConcreteObserver observer1 = new ConcreteObserver("Observer 1");ConcreteObserver observer2 = new ConcreteObserver("Observer 2");ConcreteObserver observer3 = new ConcreteObserver("Observer 3");subject.addObserver(observer1);subject.addObserver(observer2);subject.addObserver(observer3);subject.setMessage("Hello, observers!");}}
在这个示例中,我们使用了 ExecutorService 的线程池来实现异步非阻塞的通知。 每个观察者更新操作都将作为一个任务提交给线程池并异步执行。
跨进程通信
● 不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程内的实现方式。
● 跨进程通信可以引入中间件(消息队列)来实现,被观察者和观察者解耦更加彻底,两部分的耦合更小