观察者(订阅)模式"/>
观察者(订阅)模式
文章目录
- 思考观察者模式
- 1.观察者模式的本质
- 2.何时选用观察者模式
- 3.优缺点
- 4.观察者模式的结构
- 5.实现
- 模拟手机降价通知用户
- JDK观察者模式
- 模拟摇号通知客户
思考观察者模式
观察者模式是典型的发布订阅模式,当一个东西有变化了,就通知所有订阅他的人
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;@Overridepublic 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();//初始价格为150subject.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;@Overridepublic 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();//初始价格为150subject2.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{@Overridepublic void doEvent(LotteryResult result) {log.info("记录用户 {}摇号结果(MQ): {}", result.getUId(),result.getMsg());}
}/*** @description:短信监听器*/
@Slf4j
public class MessageEventListener implements EventListener{@Overridepublic 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, EventListenerlistener) {List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 退订监听器*/public void unsubscribe(Enum<EventType> eventType, EventListenerlistener) {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, newMQEventListener());eventManager.subscribe(EventManager.EventType.Message, newMessageEventListener());}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 {@Overrideprotected 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"}
更多推荐
观察者(订阅)模式
发布评论