观察者模式的应用场景

观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有以依赖者(观察者)都会受到通知并更新,属于行为型模式。

观察者模式有时也叫作发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用得也非常广泛,比如微信朋友圈动态通知、页面的消息通知、邮件通知、广播通知等。

JDK API

当“小伙伴们”在 GPer 生态圈中提问的时候,如果设置了指定老师回答,对应的老师就会收到邮件通知,这就是观察者模式的一种应用场景。其实 JDK 本身就提供了这样的 API。我们用代码来还原一下这个应用场景,创建 GPer 类:

  1. package com.yjw.demo.pattern.observer.GPer_java;
  2. import java.util.Observable;
  3. public class GPer extends Observable {
  4. private String name = "GPer 生态圈";
  5. private static GPer gper = null;
  6. private GPer() {
  7. }
  8. public static GPer getInstance() {
  9. if (null == gper) {
  10. gper = new GPer();
  11. }
  12. return gper;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void publishQuestion(Question question) {
  18. System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题。");
  19. setChanged();
  20. notifyObservers(question);
  21. }
  22. }

创建文体类 Question:

  1. package com.yjw.demo.pattern.observer.GPer_java;
  2. public class Question {
  3. private String userName;
  4. private String content;
  5. public String getUserName() {
  6. return userName;
  7. }
  8. public void setUserName(String userName) {
  9. this.userName = userName;
  10. }
  11. public String getContent() {
  12. return content;
  13. }
  14. public void setContent(String content) {
  15. this.content = content;
  16. }
  17. }

创建老师类 Teacher:

  1. package com.yjw.demo.pattern.observer.GPer_java;
  2. import java.util.Observable;
  3. import java.util.Observer;
  4. public class Teacher implements Observer {
  5. private String name;
  6. public Teacher(String name) {
  7. this.name = name;
  8. }
  9. @Override
  10. public void update(Observable o, Object arg) {
  11. GPer gper = (GPer) o;
  12. Question question = (Question) arg;
  13. System.out.println("========================");
  14. System.out.println(name + "老师,你好!\n" + "您收到了一个来自“" + gper.getName()
  15. + "”的提问,希望您解答,问题内容如下:\n" + question.getContent()
  16. + "\n" + "提问者:" + question.getUserName());
  17. }
  18. }

客户端测试代码如下:

  1. package com.yjw.demo.pattern.observer.GPer_java;
  2. public class ObserverTest {
  3. public static void main(String[] args) {
  4. GPer gper = GPer.getInstance();
  5. Teacher tom = new Teacher("Tom");
  6. Teacher mic = new Teacher("Mic");
  7. gper.addObserver(tom);
  8. gper.addObserver(mic);
  9. // 业务逻辑代码
  10. Question question = new Question();
  11. question.setUserName("小明");
  12. question.setContent("观察者设计模式适用于哪些场景?");
  13. gper.publishQuestion(question);
  14. }
  15. }

运行结果如下图所示。

image.png

手动实现

为了更加深刻地了解观察者模式的实现原理,我们设计一个监听鼠标事件的观察者模式。

首先,创建 Event 类:

  1. package com.yjw.demo.pattern.observer.mouse.core;
  2. import java.lang.reflect.Method;
  3. /**
  4. * 事件内容
  5. */
  6. public class Event {
  7. // 事件源,事件由谁发起的,保存起来
  8. private Object source;
  9. // 事件触发,要通知谁
  10. private Object target;
  11. // 事件触发,要做什么动作,回调
  12. private Method callback;
  13. // 事件的名称,触发是什么事件
  14. private String trigger;
  15. // 事件触发的时间
  16. private long time;
  17. public Event(Object target, Method callback) {
  18. super();
  19. this.target = target;
  20. this.callback = callback;
  21. }
  22. public Object getSource() {
  23. return source;
  24. }
  25. public void setSource(Object source) {
  26. this.source = source;
  27. }
  28. public Object getTarget() {
  29. return target;
  30. }
  31. public void setTarget(Object target) {
  32. this.target = target;
  33. }
  34. public Method getCallback() {
  35. return callback;
  36. }
  37. public void setCallback(Method callback) {
  38. this.callback = callback;
  39. }
  40. public String getTrigger() {
  41. return trigger;
  42. }
  43. public void setTrigger(String trigger) {
  44. this.trigger = trigger;
  45. }
  46. public long getTime() {
  47. return time;
  48. }
  49. public void setTime(long time) {
  50. this.time = time;
  51. }
  52. @Override
  53. public String toString() {
  54. return "Event{" +
  55. "source=" + source +
  56. ", target=" + target +
  57. ", callback=" + callback +
  58. ", trigger='" + trigger + '\'' +
  59. ", time=" + time +
  60. '}';
  61. }
  62. }

创建 EventLisenter 类:

  1. package com.yjw.demo.pattern.observer.mouse.core;
  2. import java.lang.reflect.Method;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. /**
  6. * 监听器,它就是观察者的桥梁
  7. */
  8. public class EventListener {
  9. // JDK 底层的Listener通常也是这样设计的
  10. private Map<String, Event> events = new HashMap<>();
  11. /**
  12. * 添加事件监听
  13. *
  14. * @param eventType
  15. * @param target
  16. */
  17. public void addListener(String eventType, Object target) {
  18. try {
  19. this.addListener(eventType, target,
  20. target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
  21. } catch (NoSuchMethodException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. public void addListener(String eventType, Object target, Method callback) {
  26. // 注册事件
  27. events.put(eventType, new Event(target, callback));
  28. }
  29. /**
  30. * 触发事件
  31. *
  32. * @param event
  33. */
  34. public void trigger(Event event) {
  35. event.setSource(this);
  36. event.setTime(System.currentTimeMillis());
  37. try {
  38. // 发起回调
  39. if (event.getCallback() != null) {
  40. event.getCallback().invoke(event.getTarget(), event);
  41. }
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. /**
  47. * 触发事件
  48. *
  49. * @param trigger
  50. */
  51. protected void trigger(String trigger) {
  52. if (!this.events.containsKey((trigger))) {
  53. return;
  54. }
  55. trigger(this.events.get(trigger).setTrigger(trigger));
  56. }
  57. private String toUpperFirstCase(String str) {
  58. char[] chars = str.toCharArray();
  59. chars[0] -= 32;
  60. return String.valueOf(chars);
  61. }
  62. }

创建 MouseEventType 接口:

  1. package com.yjw.demo.pattern.observer.mouse;
  2. /**
  3. * 鼠标事件类型
  4. */
  5. public interface MouseEventType {
  6. // 单击
  7. String ON_CLICK = "click";
  8. // 双击
  9. String ON_DOUBLE_CLICK = "doubleClick";
  10. // 弹起
  11. String ON_UP = "up";
  12. // 按下
  13. String ON_DOWN = "down";
  14. // 移动
  15. String ON_MOVE = "move";
  16. // 滚动
  17. String ON_WHEEL = "wheel";
  18. // 悬停
  19. String ON_OVER = "over";
  20. // 失焦
  21. String ON_BLUR = "blur";
  22. // 获焦
  23. String ON_FOCUS = "focus";
  24. }

创建 Mouse 类:

  1. package com.yjw.demo.pattern.observer.mouse;
  2. import com.yjw.demo.pattern.observer.mouse.core.EventListener;
  3. /**
  4. * 鼠标
  5. *
  6. * <pre>
  7. * 通常的话,采用动态代理来实现监控,避免了代码侵入
  8. * </pre>
  9. *
  10. * @author yinjianwei
  11. * @date 2019/01/04
  12. */
  13. public class Mouse extends EventListener {
  14. public void click() {
  15. System.out.println("调用单击方法");
  16. trigger(MouseEventType.ON_CLICK);
  17. }
  18. public void doubleClick() {
  19. System.out.println("调用双击方法");
  20. trigger(MouseEventType.ON_DOUBLE_CLICK);
  21. }
  22. public void up() {
  23. System.out.println("调用弹起方法");
  24. trigger(MouseEventType.ON_UP);
  25. }
  26. public void down() {
  27. System.out.println("调用按下方法");
  28. trigger(MouseEventType.ON_DOWN);
  29. }
  30. public void move() {
  31. System.out.println("调用移动方法");
  32. trigger(MouseEventType.ON_MOVE);
  33. }
  34. public void wheel() {
  35. System.out.println("调用滚动方法");
  36. trigger(MouseEventType.ON_WHEEL);
  37. }
  38. public void over() {
  39. System.out.println("调用悬停方法");
  40. trigger(MouseEventType.ON_OVER);
  41. }
  42. public void blur() {
  43. System.out.println("调用失焦方法");
  44. trigger(MouseEventType.ON_BLUR);
  45. }
  46. public void focus() {
  47. System.out.println("调用获焦方法");
  48. trigger(MouseEventType.ON_FOCUS);
  49. }
  50. }

创建 MouseEventCallback 类:

  1. package com.yjw.demo.pattern.observer.mouse;
  2. import com.yjw.demo.pattern.observer.mouse.core.Event;
  3. /**
  4. * 观察者,观察鼠标动作
  5. *
  6. * @author yinjianwei
  7. * @date 2019/01/04
  8. */
  9. public class MouseEventCallback {
  10. public void onClick(Event event) {
  11. System.out.println("==========触发鼠标单击事件==========" + "\n" + event);
  12. }
  13. public void onDoubleClick(Event event) {
  14. System.out.println("==========触发鼠标双击事件==========" + "\n" + event);
  15. }
  16. public void onUp(Event event) {
  17. System.out.println("==========触发鼠标弹起事件==========" + "\n" + event);
  18. }
  19. public void onDown(Event event) {
  20. System.out.println("==========触发鼠标按下事件==========" + "\n" + event);
  21. }
  22. public void onMove(Event event) {
  23. System.out.println("==========触发鼠标移动事件==========" + "\n" + event);
  24. }
  25. public void onWheel(Event event) {
  26. System.out.println("==========触发鼠标滚动事件==========" + "\n" + event);
  27. }
  28. public void onOver(Event event) {
  29. System.out.println("==========触发鼠标悬停事件==========" + "\n" + event);
  30. }
  31. public void onBlur(Event event) {
  32. System.out.println("==========触发鼠标失焦事件==========" + "\n" + event);
  33. }
  34. public void onFocus(Event event) {
  35. System.out.println("==========触发鼠标获焦事件==========" + "\n" + event);
  36. }
  37. }

客户端测试代码如下:

  1. package com.yjw.demo.pattern.observer.mouse;
  2. import java.lang.reflect.Method;
  3. import com.yjw.demo.pattern.observer.mouse.core.Event;
  4. /**
  5. * 鼠标事件测试
  6. *
  7. * @author yinjianwei
  8. * @date 2019/01/04
  9. */
  10. public class MouseEventTest {
  11. public static void main(String[] args) {
  12. try {
  13. MouseEventCallback mouseEventCallback = new MouseEventCallback();
  14. // 注册事件
  15. Mouse mouse = new Mouse();
  16. mouse.addListener(MouseEventType.ON_CLICK, mouseEventCallback);
  17. mouse.addListener(MouseEventType.ON_DOUBLE_CLICK, mouseEventCallback);
  18. mouse.addListener(MouseEventType.ON_UP, mouseEventCallback);
  19. mouse.addListener(MouseEventType.ON_DOWN, mouseEventCallback);
  20. mouse.addListener(MouseEventType.ON_MOVE, mouseEventCallback);
  21. mouse.addListener(MouseEventType.ON_WHEEL, mouseEventCallback);
  22. mouse.addListener(MouseEventType.ON_OVER, mouseEventCallback);
  23. mouse.addListener(MouseEventType.ON_BLUR, mouseEventCallback);
  24. mouse.addListener(MouseEventType.ON_FOCUS, mouseEventCallback);
  25. // 调用方法
  26. mouse.click();
  27. mouse.doubleClick();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }

运行结果如下图所示。

image.png

观察者模式的优缺点

观察者模式的优点如下:

  1. 在观察者和被观察者之间建立了一个抽象的耦合。
  2. 观察者模式支持广播通信。

观察者模式的缺点如下:

  1. 观察者之间有过多的细节依赖、时间消耗多,程序的复杂性更高。
  2. 使用不当会出现循环调用。

摘录:《Spring 5 核心原理与30个类手写实战》来自文艺界的Tom老师的书籍。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/hfzxhi 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。