思考观察者模式
观察者模式是典型的发布订阅模式,当一个东西有变化了,就通知所有订阅他的人
1.观察者模式的本质
观察者模式的本质:触发联动。
观察者模式的本质是解耦主题和观察者之间的关系,实现了对象之间的松耦合。主题对象并不知道具体的观察者对象,它只负责发送通知。观察者对象也不需要知道具体的主题对象,它只需要注册为主题的观察者并等待通知。这样可以提高系统的灵活性和可扩展性,方便添加、删除和修改观察者对象,同时也降低了主题对象和观察者对象之间的耦合度。
总而言之,观察者模式的本质是通过定义一对多的依赖关系,实现了对象之间的消息传递和通知机制,从而实现了对象状态的变化通知和自动更新。
2.何时选用观察者模式
建议在以下情况中选用观察者模式。
-
当一个对象的状态改变需要通知其他对象,并且不希望对象之间紧密耦合时,可以选用观察者模式。该模式能够解耦主题对象和观察者对象之间的关系,使得它们可以独立演化。
-
当一个对象的状态改变需要触发一系列相关操作或更新时,可以选用观察者模式。通过将这些操作或更新封装在观察者对象中,可以实现逻辑的解耦和聚集,便于管理和维护。
-
当一个对象的状态改变需要通知一组相关对象时,可以选用观察者模式。观察者模式允许多个观察者对象订阅主题对象,以便在状态变化时接收通知并采取相应行动。
-
当需要在运行时动态地添加、删除和管理观察者对象时,可以选用观察者模式。通过观察者模式,可以灵活地添加和移除观察者对象,而不影响主题对象的实现。
3.优缺点
观察者模式具有以下优点。
- 解耦对象间的依赖关系:观察者模式能够将观察者(订阅者)和被观察者(发布者)对象解耦,使得它们之间的依赖关系松散化。被观察者只需要知道观察者的接口,而不需要知道具体的观察者是谁。
- 支持广播通信:被观察者对象可以同时通知多个观察者对象,使得信息能够广播给多个对象。这样可以方便地实现事件驱动的系统。
- 符合开闭原则:在观察者模式中,新增观察者或者删除观察者都比较容易,而不需要修改被观察者的代码。这样可以增加系统的灵活性和可扩展性。
观察者模式的缺点是:
- 可能引入循环引用:在观察者模式中,如果观察者和被观察者之间发生循环引用,可能会导致内存泄漏问题。需要特别注意避免循环引用的发生。
4.观察者模式的结构
- Subject:目标对象,通常具有如下功能。
一个目标可以被多个观察者观察。
目标提供对观察者注册和退订的维护。
当目标的状态发生变化时,目标负责通知所有注册的、有效的观察者。 - Observer:定义观察者的接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里面回调目标对象,以获取目标对象的数据。
- ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的、有效的观察者,让观察者执行相应的处理。
- ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持和目标的相应状态一致。
5.实现
模拟:手机降价通知用户
模拟手机降价通知用户
1.目标类
/**
* @description:通用目标类,也可为抽象类
*/
public class Subject {
/**
* 维护一个观察者列表
*/
private List<Observer> list=new ArrayList<>();
/**
* 注册观察者
* @param observer
*/
public void attach(Observer observer){
list.add(observer);
}
/**
* 移除观察者
* @param observer
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知观察者
*/
public void notifyObservers(){
list.stream().forEach(obj->obj.update(this));
}
}
/**
* @description:手机目标对象
*/
@Getter
@ToString
public class PhoneSubject extends Subject{
/**
* 手机价格
*/
private double price;
public void setPrice(double price) {
this.price = price;
//价格小于150为降价
if (price<150){
//通知用户降价
notifyObservers();
}
}
}
2.观察者类
/**
* @description:观察者接口
*/
public interface Observer {
/**
* 被通知的方法
* @param subject 目标对象
*/
void update(Subject subject);
}
/**
* @description:用户(观察者)
*/
@Data
public class UserObserver implements Observer{
/**
* 用户名
*/
private String name;
@Override
public void update(Subject subject) {
System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject)subject).getPrice());
}
}
3.测试类
/**
* @description:测试类
*/
public class Client {
public static void main(String[] args) {
UserObserver userObserver1 = new UserObserver();
userObserver1.setName("张三");
UserObserver userObserver2 = new UserObserver();
userObserver2.setName("李四");
PhoneSubject subject=new PhoneSubject();
//初始价格为150
subject.setPrice(150);
//绑定观察者
subject.attach(userObserver1);
subject.attach(userObserver2);
//涨价不通知
subject.setPrice(250);
//降价才通知用户,第一次降价
subject.setPrice(100);
//降价才通知用户,第二次降价
subject.setPrice(88);
}
}
4.效果
JDK观察者模式
1.目标类继承Observable接口即可
/**
* @description:手机目标对象
*/
@Getter
@ToString
public class PhoneSubject2 extends Observable {
/**
* 手机价格
*/
private double price;
public void setPrice(double price) {
this.price = price;
//价格小于150为降价
if (price<150){
//通知用户降价
notifyObservers();
}
}
}
里面维护了观察者对象,以及注册、移除观察者,通知观察者等
2.观察者类实现Observer接口即可
/**
* @description:用户1
*/
@Data
public class UserObserver2 implements Observer {
/**
* 用户名
*/
private String name;
@Override
public void update(Observable o, Object arg) {
if (o instanceof PhoneSubject2){
System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject2)o).getPrice());
}
}
}
和手写的一模一样
3.测试类
/**
* @author conggq
* @description:测试类
* @createTime 2022/11/29 17:27
*/
public class Client {
public static void main(String[] args) {
UserObserver userObserver1 = new UserObserver();
userObserver1.setName("张三");
UserObserver userObserver2 = new UserObserver();
userObserver2.setName("李四");
PhoneSubject2 subject2=new PhoneSubject2();
//初始价格为150
subject2.setPrice(150);
//涨价不通知
subject.setPrice(250);
//降价才通知用户,第一次降价
subject.setPrice(100);
//降价才通知用户,第二次降价
subject.setPrice(88);
}
}
结果一样
模拟摇号通知客户
1.监听器类
/**
* @description:事件监听接口
*/
public interface EventListener {
void doEvent(LotteryResult result);
}
/**
* @description:MQ监听器
*/
@Slf4j
public class MQEventListener implements EventListener{
@Override
public void doEvent(LotteryResult result) {
log.info("记录用户 {}摇号结果(MQ): {}", result.getUId(),
result.getMsg());
}
}
/**
* @description:短信监听器
*/
@Slf4j
public class MessageEventListener implements EventListener{
@Override
public void doEvent(LotteryResult result) {
log.info("给用户 {} 发送短信通知(短信) : {}", result.getUId(),
result.getMsg());
}
}
2.事件管理器
public class EventManager {
Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();
public EventManager(Enum<EventType>... operations) {
for (Enum<EventType> operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
public enum EventType {
MQ, Message
}
/**
* 订阅监听器
*/
public void subscribe(Enum<EventType> eventType, EventListener
listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
/**
* 退订监听器
*/
public void unsubscribe(Enum<EventType> eventType, EventListener
listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
/**
* 通知监听器
*/
public void notify(Enum<EventType> eventType, LotteryResult result) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.doEvent(result);
}
}
}
3.业务类
public abstract class LotteryService {
private EventManager eventManager;
public LotteryService() {
eventManager = new EventManager(EventManager.EventType.MQ,
EventManager.EventType.Message);
eventManager.subscribe(EventManager.EventType.MQ, new
MQEventListener());
eventManager.subscribe(EventManager.EventType.Message, new
MessageEventListener());
}
public LotteryResult draw(String uId) {
LotteryResult result = doDraw(uId);
//根据需要通知那个监听器
eventManager.notify(EventManager.EventType.MQ, result);
eventManager.notify(EventManager.EventType.Message, result);
return result;
}
protected abstract LotteryResult doDraw(String uId);
}
public class LotteryServiceImpl extends LotteryService {
@Override
protected LotteryResult doDraw(String uId) {
String lottery = lottery(uId);
return new LotteryResult(uId, lottery, new Date());
}
public String lottery(String uId) {
return Math.abs(uId.hashCode()) % 2 == 0
? "恭喜你,编号 ".concat(uId).concat(" 在本次摇号中签")
: "很遗憾,编号 ".concat(uId).concat(" 在本次摇号未中签或摇号资格已过期 ");
}
}
4.返回封装类
@Data
@AllArgsConstructor
public class LotteryResult {
private String uId;
private String msg;
private Date dateTime;
}
5.测试类
@Slf4j
public class LotteryTest {
public static void main(String[] args) {
LotteryService lotteryService = new LotteryServiceImpl();
LotteryResult result = lotteryService.draw("2765789109876");
log.info("测试结果 {}", JSON.toJSONString(result));
}
}
5.结果
MQEventListener - 记录用户 2765789109876摇号结果(MQ): 很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期
MessageEventListener - 给用户 2765789109876 发送短信通知(短信) : 很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期
LotteryTest - 测试结果 {"dateTime":1685430399322,"msg":"很遗憾,编号 2765789109876 在本次摇号未中签或摇号资格已过期 ","uId":"2765789109876"}