经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
设计模式-观察者模式
来源:cnblogs  作者:studyMore  时间:2018/12/5 9:50:13  对本文有异议

2017-10-04 10:26:11

当我们在 github 上看到一个很好的项目时,也许会想要持续地跟进这个项目的最新进度。
当你按下 Watch 按钮时,每当项目更新,那么 github 就会不断地将这个项目的最新进展通过邮件发送给你,提醒你。
这就是我们今天谈论的 观察者模式

请记住:任何模式的具体实现并不是一成不变的,根据具体的情况去改变

github 中有数据是我们所关心的,我们把 github 当作一个大主题,而我们则是观察者。

这个例子的 主题-观察者类图:

我们并不知道会有多少不同的主题会被别人关注,因此最好遵循    面向接口编程,不面向实现编程 这个原则

  1. 1 //主题
  2. 2 public interface Subject {
  3. 3
  4. 4 /**
  5. 5 * 注册成为观察者
  6. 6 * @param observer 观察者对象
  7. 7 */
  8. 8 public void regitster(Observer observer);
  9. 9
  10. 10 /**
  11. 11 * 移除观察者
  12. 12 * @param observer 观察者对象
  13. 13 */
  14. 14 public void remove(Observer observer);
  15. 15
  16. 16 /**
  17. 17 * 将最新消息通知观察者
  18. 18 */
  19. 19 public void notifyObservers();
  20. 20 }
主题接口

 

上面这个接口是主题接口(也就是被观察者),很明显主题必须有添加、删除观察者以及将最新消息通知观察者等三个方法。

Observer 是观察者接口,主题要想添加或删除观察者,就要有观察者对象作为参数。

接下来的代码是观察者接口:

  1. 1 //观察者
  2. 2 public interface Observer {
  3. 3 /**
  4. 4 * 当主题通知时,观察者接收最新的数据以用来自己更新
  5. 5 * @param data 最新的数据
  6. 6 */
  7. 7 public void update(Object data);
  8. 8 }
观察者接口


然后是具体的主题和观察者啦

现在在 javaWeb 中最有名的框架无疑就是 Spring 啦,我们就以 SpringSubject 作为具体主题的例子

  1. 1 //Spring 主题:SpringSubject
  2. 2 public class SpringSubject implements Subject {
  3. 3
  4. 4 /**
  5. 5 * 观察者列表
  6. 6 */
  7. 7 private List<Observer> observers;
  8. 8
  9. 9 /**
  10. 10 * 主题持有的所有数据
  11. 11 */
  12. 12 private String data;
  13. 13
  14. 14 public SpringSubject(){
  15. 15 observers = new ArrayList<Observer>();
  16. 16 }
  17. 17
  18. 18 /**
  19. 19 * 注册成为观察者
  20. 20 *
  21. 21 * @param observer 注册的观察者对象
  22. 22 */
  23. 23 @Override
  24. 24 public void regitster(Observer observer) {
  25. 25 if(!observers.contains(observer)) {
  26. 26 observers.add(observer);
  27. 27 System.out.println(observer + "成为了 Spring 的观察者");
  28. 28 }
  29. 29 }
  30. 30
  31. 31 /**
  32. 32 * 移除观察者
  33. 33 *
  34. 34 * @param observer 需要移除的观察者
  35. 35 */
  36. 36 @Override
  37. 37 public void remove(Observer observer) {
  38. 38 if(observers.contains(observer)){
  39. 39 observers.remove(observer);
  40. 40 System.out.println(observer + " 不再是 Spring 的观察者啦!");
  41. 41 }
  42. 42 }
  43. 43
  44. 44 /**
  45. 45 * 将最新消息通知观察者
  46. 46 */
  47. 47 @Override
  48. 48 public void notifyObservers() {
  49. 49 System.out.println("哇!有最新消息啦,赶快通知所有的观察者");
  50. 50 for (Observer o : observers) {
  51. 51 o.update(data);
  52. 52 }
  53. 53 }
  54. 54
  55. 55 /**
  56. 56 * 当数据改变时
  57. 57 */
  58. 58 public void setChanged(){
  59. 59 notifyObservers();
  60. 60 }
  61. 61 }
SpringSubject

 

我们的猫咪观察者出场了!

  1. 1 //一个好奇的观察者
  2. 2 //这里有一只小猫咪对 Spring 很感兴趣,它于是成为了 Spring 的观察者!
  3. 3 public class CatObserver implements Observer {
  4. 4
  5. 5 /**
  6. 6 * 观察者所持有的数据
  7. 7 */
  8. 8 private Object data;
  9. 9
  10. 10 /**
  11. 11 * 这里加上一个主题的域,如果有读者认为不需要的话,也可以自己实现一个
  12. 12 * 这个主题域,可以更方便的增加和删除观察者
  13. 13 */
  14. 14 private Subject subject;
  15. 15
  16. 16 public CatObserver(Subject subject){
  17. 17 this.subject = subject;
  18. 18 //猫咪如愿以偿的成为 spring 的粉丝啦
  19. 19 this.subject.regitster(this);
  20. 20 }
  21. 21
  22. 22 /**
  23. 23 * 小猫咪不想再当 spring 的观察者了!一点也不好玩
  24. 24 */
  25. 25 public void remove(){
  26. 26 this.subject.remove(this);
  27. 27 this.subject = null;
  28. 28 }
  29. 29
  30. 30 /**
  31. 31 * 当主题通知时,观察者接收最新的数据以用来自己更新
  32. 32 *
  33. 33 * @param data 最新的数据
  34. 34 */
  35. 35 @Override
  36. 36 public void update(Object data) {
  37. 37 System.out.println("小猫咪接受到最新的数据啦");
  38. 38 this.data = data;
  39. 39 }
  40. 40 }
CatObserver

 

到现在为止,我们就构建好了。最好就是测试代码啦

  1. 1 //这是测试代码
  2. 2 public class ObserverMain {
  3. 3
  4. 4 public static void main(String[] args) {
  5. 5 //Spring 主题诞生了!
  6. 6 Subject subject = new SpringSubject();
  7. 7 //然后我们的猫咪,它一出来就迫不及待地成为 spring 主题的粉丝
  8. 8 Observer cat = new CatObserver(subject);
  9. 9 //数据改变
  10. 10 ((SpringSubject) subject).setChanged();
  11. 11 //小猫咪退出了
  12. 12 ((CatObserver) cat).remove();
  13. 13 }
  14. 14 }
ObserverMain

这段代码显得很丑陋,强制转换用了两次,用 jdk5 出现的泛型修改下代码便可

测试结果如下:

控制通知的频率

听小猫咪说,它之所以退出,是因为 Spring 主题更新太频繁了
因此,我们现在修改下这个程序,Spring 主题每次更新一个大版本时在通知消息,小版本不通知

  1. 1 //下面是改进的版本:SpringSubject
  2. 2 【代码】
  3. 3 public class SpringSubject implements Subject {
  4. 4
  5. 5 /**
  6. 6 * 观察者列表
  7. 7 */
  8. 8 private List<Observer> observers;
  9. 9
  10. 10 /**
  11. 11 * 主题所持有的所有数据
  12. 12 */
  13. 13 private String data;
  14. 14
  15. 15 /**
  16. 16 * 是否通知
  17. 17 */
  18. 18 private boolean isNotified = false;
  19. 19
  20. 20 public SpringSubject(){
  21. 21 observers = new ArrayList<Observer>();
  22. 22 }
  23. 23
  24. 24 /**
  25. 25 * 注册成为观察者
  26. 26 *
  27. 27 * @param observer 观察者对象
  28. 28 */
  29. 29 @Override
  30. 30 public void regitster(Observer observer) {
  31. 31 if(!observers.contains(observer)) {
  32. 32 observers.add(observer);
  33. 33 System.out.println(observer + "成为了 Spring 的观察者");
  34. 34 }
  35. 35 }
  36. 36
  37. 37 /**
  38. 38 * 移除观察者
  39. 39 *
  40. 40 * @param observer
  41. 41 */
  42. 42 @Override
  43. 43 public void remove(Observer observer) {
  44. 44 if(observers.contains(observer)){
  45. 45 observers.remove(observer);
  46. 46 System.out.println(observer + " 不再是 Spring 的观察者啦!");
  47. 47 }
  48. 48 }
  49. 49
  50. 50 /**
  51. 51 * 将最新消息通知观察者
  52. 52 */
  53. 53 @Override
  54. 54 public void notifyObservers() {
  55. 55 System.out.println("哇!有最新消息啦,赶快通知所有的观察者");
  56. 56 for (Observer o : observers) {
  57. 57 o.update(data);
  58. 58 }
  59. 59 }
  60. 60
  61. 61 /**
  62. 62 * 在这里我们就可以控制更新的频率啦
  63. 63 */
  64. 64 public void setNotified(){
  65. 65 isNotified = true;
  66. 66 }
  67. 67
  68. 68 /**
  69. 69 * 当数据改变时
  70. 70 */
  71. 71 public void setChanged(){
  72. 72 setNotified();
  73. 73 if(isNotified) {
  74. 74 notifyObservers();
  75. 75 }
  76. 76 isNotified = false;
  77. 77 }
  78. 78 }
改进后的 SpringSubject


isNotified 这个域就是为了通知不那么频繁而设立的,实际情况根据需要使用

推拉
其实不一定要我们把数据 推送给 观察者,也可以观察者自己需要数据时,自己来拿,也很好的

这就是观察者模式的 推 和 拉 两种方式啦

推:主题会把自己所有的数据推送给任何一个观察者,不管这个观察者需不需要所有的数据

拉:每当观察者需要最新数据时,观察者都会自己去对应的主题拉走自己需要的数据

观察者模式的 拉 这个方式,由于观察者是主动的,所以 update 这个方法应该有主题的这个参数,下面重写 Observer 和 CatObserver:

  1. 1 //观察者
  2. 2 public interface Observer {
  3. 3 /**
  4. 4 * 当主题通知时,观察者接收最新的数据以用来自己更新
  5. 5 *
  6. 6 * @param data 最新的数据
  7. 7 */
  8. 8 public void update(Object data);
  9. 9
  10. 10 /**
  11. 11 * 当观察者自己需要最新的数据时,观察者自己去主题内部取自己需要的数据
  12. 12 * @param subject 观察者依赖的数据的对应主题
  13. 13 */
  14. 14 public void update(Subject subject);
  15. 15 }
Observer
  1. 1 public class CatObserver implements Observer {
  2. 2
  3. 3 /**
  4. 4 * 观察者所持有的数据
  5. 5 */
  6. 6 private Object data;
  7. 7
  8. 8 /**
  9. 9 * 这里加上一个主题的域,如果有读者认为不需要的话,也可以自己实现一个
  10. 10 * 这里有主题的域,可以更方便的增加和删除观察者
  11. 11 */
  12. 12 private Subject subject;
  13. 13
  14. 14 public CatObserver(Subject subject){
  15. 15 this.subject = subject;
  16. 16 //猫咪如愿以偿的成为 spring 的粉丝啦
  17. 17 this.subject.regitster(this);
  18. 18 }
  19. 19
  20. 20 /**
  21. 21 * 小猫咪不想再当 spring 的观察者了!一点也不好玩
  22. 22 */
  23. 23 public void remove(){
  24. 24 this.subject.remove(this);
  25. 25 this.subject = null;
  26. 26 }
  27. 27
  28. 28 /**
  29. 29 * 当主题通知时,观察者接收最新的数据以用来自己更新
  30. 30 *
  31. 31 * @param data 最新的数据
  32. 32 */
  33. 33 @Override
  34. 34 public void update(Object data) {
  35. 35 System.out.println("小猫咪接受到最新的数据啦");
  36. 36 this.data = subject.data;
  37. 37 }
  38. 38
  39. 39 /**
  40. 40 * 当观察者自己需要最新的数据时,观察者自己去主题内部取自己需要的数据
  41. 41 */
  42. 42 @Override
  43. 43 public void update(Subject subject) {
  44. 44 SpringSubject s = (SpringSubject)subject;
  45. 45 this.data = s.getData();
  46. 46 }
  47. 47 /**
  48. 48 * 获取主题
  49. 49 * @return
  50. 50 * 观察者主题
  51. 51 */
  52. 52 public Subject getSubject(){
  53. 53 return this.subject;
  54. 54 }
  55. 55 }
CatObserver

 

最后的这个例子的类图如下:

这个模式我们就说完啦,来总结观察者啦

总结:


观察者模式:定义对象间一对多的依赖关系,当一个对象发生变化时,依赖其的多个对象都会收到通知并自动更新

  观察者模式优点:
        抽象主题只依赖于抽象观察者
        观察者模式使信息产生层和响应层分离

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号