• 第十五章 观察者模式


    目录

    1 观察者模式介绍

    2 观察者模式原理

    3 观察者模式实现

    4 观察者模式应用实例

     5 观察者模式总结


    1 观察者模式介绍

    观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.

    现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

    观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

    2 观察者模式原理

    • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
    • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
    • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
    • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.

    3 观察者模式实现

    • 观察者
    1. /**
    2. * 抽象观察者
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public interface Observer {
    7. //update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    8. public void update();
    9. }
    10. /**
    11. * 具体观察者
    12. * @author spikeCong
    13. * @date 2022/10/11
    14. **/
    15. public class ConcreteObserverOne implements Observer {
    16. @Override
    17. public void update() {
    18. //获取消息通知,执行业务代码
    19. System.out.println("ConcreteObserverOne 得到通知!");
    20. }
    21. }
    22. /**
    23. * 具体观察者
    24. * @author spikeCong
    25. * @date 2022/10/11
    26. **/
    27. public class ConcreteObserverTwo implements Observer {
    28. @Override
    29. public void update() {
    30. //获取消息通知,执行业务代码
    31. System.out.println("ConcreteObserverTwo 得到通知!");
    32. }
    33. }
    • 被观察者
    1. /**
    2. * 抽象目标类
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public interface Subject {
    7. void attach(Observer observer);
    8. void detach(Observer observer);
    9. void notifyObservers();
    10. }
    11. /**
    12. * 具体目标类
    13. * @author spikeCong
    14. * @date 2022/10/11
    15. **/
    16. public class ConcreteSubject implements Subject {
    17. //定义集合,存储所有观察者对象
    18. private ArrayList observers = new ArrayList<>();
    19. //注册方法,向观察者集合中增加一个观察者
    20. @Override
    21. public void attach(Observer observer) {
    22. observers.add(observer);
    23. }
    24. //注销方法,用于从观察者集合中删除一个观察者
    25. @Override
    26. public void detach(Observer observer) {
    27. observers.remove(observer);
    28. }
    29. //通知方法
    30. @Override
    31. public void notifyObservers() {
    32. //遍历观察者集合,调用每一个观察者的响应方法
    33. for (Observer obs : observers) {
    34. obs.update();
    35. }
    36. }
    37. }
    • 测试类
    1. public class Client {
    2. public static void main(String[] args) {
    3. //创建目标类(被观察者)
    4. ConcreteSubject subject = new ConcreteSubject();
    5. //注册观察者类,可以注册多个
    6. subject.attach(new ConcreteObserverOne());
    7. subject.attach(new ConcreteObserverTwo());
    8. //具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
    9. subject.notifyObservers();
    10. }
    11. }

    4 观察者模式应用实例

    1 ) 未使用设计模式

    1. /**
    2. * 模拟买房摇号服务
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public class DrawHouseService {
    7. //摇号抽签
    8. public String lots(String uId){
    9. if(uId.hashCode() % 2 == 0){
    10. return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
    11. }else{
    12. return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";
    13. }
    14. }
    15. }
    16. public class LotteryResult {
    17. private String uId; // 用户id
    18. private String msg; // 摇号信息
    19. private Date dataTime; // 业务时间
    20. //get&set.....
    21. }
    22. /**
    23. * 开奖服务接口
    24. * @author spikeCong
    25. * @date 2022/10/11
    26. **/
    27. public interface LotteryService {
    28. //摇号相关业务
    29. public LotteryResult lottery(String uId);
    30. }
    31. /**
    32. * 开奖服务
    33. * @author spikeCong
    34. * @date 2022/10/11
    35. **/
    36. public class LotteryServiceImpl implements LotteryService {
    37. //注入摇号服务
    38. private DrawHouseService houseService = new DrawHouseService();
    39. @Override
    40. public LotteryResult lottery(String uId) {
    41. //摇号
    42. String result = houseService.lots(uId);
    43. //发短信
    44. System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);
    45. //发送MQ消息
    46. System.out.println("记录用户摇号结果(MQ), 用户ID:" + uId + ",摇号结果:" + result);
    47. return new LotteryResult(uId,result,new Date());
    48. }
    49. }
    50. @Test
    51. public void test1(){
    52. LotteryService ls = new LotteryServiceImpl();
    53. String result = ls.lottery("1234567887654322");
    54. System.out.println(result);
    55. }

    1 ) 使用观察者模式进行优化

    • 事件监听
    1. /**
    2. * 事件监听接口
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public interface EventListener {
    7. void doEvent(LotteryResult result);
    8. }
    9. /**
    10. * 短信发送事件
    11. * @author spikeCong
    12. * @date 2022/10/11
    13. **/
    14. public class MessageEventListener implements EventListener {
    15. @Override
    16. public void doEvent(LotteryResult result) {
    17. System.out.println("发送短信通知用户ID为: " + result.getuId() +
    18. ",您的摇号结果如下: " + result.getMsg());
    19. }
    20. }
    21. /**
    22. * MQ消息发送事件
    23. * @author spikeCong
    24. * @date 2022/10/11
    25. **/
    26. public class MQEventListener implements EventListener {
    27. @Override
    28. public void doEvent(LotteryResult result) {
    29. System.out.println("记录用户摇号结果(MQ), 用户ID:" + result.getuId() +
    30. ",摇号结果:" + result.getMsg());
    31. }
    32. }

     事件处理

    1. /**
    2. * 事件处理类
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public class EventManager {
    7. public enum EventType{
    8. MQ,Message
    9. }
    10. //监听器集合
    11. Map, List> listeners = new HashMap<>();
    12. public EventManager(Enum... operations) {
    13. for (Enum operation : operations) {
    14. this.listeners.put(operation,new ArrayList<>());
    15. }
    16. }
    17. /**
    18. * 订阅
    19. * @param eventType 事件类型
    20. * @param listener 监听
    21. */
    22. public void subscribe(Enum eventType, EventListener listener){
    23. List users = listeners.get(eventType);
    24. users.add(listener);
    25. }
    26. /**
    27. * 取消订阅
    28. * @param eventType 事件类型
    29. * @param listener 监听
    30. */
    31. public void unsubscribe(Enum eventType,EventListener listener){
    32. List users = listeners.get(eventType);
    33. users.remove(listener);
    34. }
    35. /**
    36. * 通知
    37. * @param eventType 事件类型
    38. * @param result 结果
    39. */
    40. public void notify(Enum eventType, LotteryResult result){
    41. List users = listeners.get(eventType);
    42. for (EventListener listener : users) {
    43. listener.doEvent(result);
    44. }
    45. }

    摇号业务处理

    1. /**
    2. * 开奖服务接口
    3. * @author spikeCong
    4. * @date 2022/10/11
    5. **/
    6. public abstract class LotteryService{
    7. private EventManager eventManager;
    8. public LotteryService(){
    9. //设置事件类型
    10. eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
    11. //订阅
    12. eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());
    13. eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());
    14. }
    15. public LotteryResult lotteryAndMsg(String uId){
    16. LotteryResult result = lottery(uId);
    17. //发送通知
    18. eventManager.notify(EventManager.EventType.Message,result);
    19. eventManager.notify(EventManager.EventType.MQ,result);
    20. return result;
    21. }
    22. public abstract LotteryResult lottery(String uId);
    23. }
    24. /**
    25. * 开奖服务
    26. * @author spikeCong
    27. * @date 2022/10/11
    28. **/
    29. public class LotteryServiceImpl extends LotteryService {
    30. //注入摇号服务
    31. private DrawHouseService houseService = new DrawHouseService();
    32. @Override
    33. public LotteryResult lottery(String uId) {
    34. //摇号
    35. String result = houseService.lots(uId);
    36. return new LotteryResult(uId,result,new Date());
    37. }
    38. }

    测试

    1. @Test
    2. public void test2(){
    3. LotteryService ls = new LotteryServiceImpl();
    4. LotteryResult result = ls.lotteryAndMsg("1234567887654322");
    5. System.out.println(result);
    6. }

     5 观察者模式总结

    1) 观察者模式的优点

    • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
    • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

    2) 观察者模式的缺点

    • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
    • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

    3 ) 观察者模式常见的使用场景

    • 当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
    • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
    • 需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
    • 微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
    • 需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

    4 ) JDK 中对观察者模式的支持

    JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.

    • java.util.Observer 接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法.

      void update(Observable o, Object arg);
      
    • java.util.Observable 类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。

      • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
      • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。

      • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

  • 相关阅读:
    【CEOI 2020】 Roads(道路)
    UML中类之间的六种主要关系
    Adobe Acrobat PDF 2024
    Java NIO中的Files类的使用
    Windows高效开发环境配置(一)
    计算机基础之计算机的发展历史
    K8s的Service详解
    python中的运算符
    关于Comparable、Comparator接口返回值决定顺序的问题
    【C++项目】手动实现一个定长内存池(了解内存分配机制、定长内存提高效率 附源码)
  • 原文地址:https://blog.csdn.net/Lin_Miao_09/article/details/139575615