定义

观察者模式(有时又被称为发布(publish)-订阅(Subscribe)模式,在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统(摘自百度百科)。

关键词:发布-订阅

为什么只有一个关键词?因为我觉得一个关键词足够说明问题了。观察者模式适用于,一个对象改变时,需要通知一个或多个其他对象,而需要通知的对象的特点是:数量不清楚,类型不清楚(仅实现了一个通用接口),具体处理方式不清楚。

举个例子来说明:

小花:你好,我开发过很多erp系统,是一位经验丰富的女司机,现在想找一份java程序员的工作...(成熟稳重型)

猎头:好的,我已经把你加入到我的程序员清单里面了,不要打电话给我,我会通知你的(好莱坞原则)

小明:本人学识渊博、经验丰富,代码风骚、效率恐怖,c/c++、java、php无不精通,熟练掌握各种框架,深山苦练20余年,一天只睡4小时,电话通知出bug后秒登vpn,千里之外定位问题,瞬息之间修复上线。 身体强壮、健步如飞,可连续编程100小时不休息,讨论技术方案5小时不喝水,上至带项目、出方案,下至盗账号、威胁pm,什么都能干......(花式装逼型)

猎头:666,我已经把你加入到我的程序员清单里面了,不要打电话给我,我会通知你的(小明和小花注册为观察者)
小花和小明继续过着自己的日子,因为他们已经在猎头的清单里面了,有工作会收到通知的。
猎头:小花同学,小明同学,这里需要一个资深的全栈工程师,创业型,弹性工作制,股票期权,年终分红...(通知所有观察者)
小明:好的(随后凭借着小明的机智,获得了这份工作)
猎头:请把介绍费汇到我的银行卡
又过了一段日子,小花凭借着自己丰富的经验,找到了工作,并没有依靠猎头,所以也就没有什么介绍费
小花:我已经找到工作了,请不要再给我发招聘信息了(移除观察者)
10年之后...
小花娶妻生子,迎娶白富美,出任CEO,走上人生巅峰,孩子已经快要1米高了....(等等,好像有什么地方不对)
小明由于天天加班,1天只睡4个小时,坟头草已经1米高了...
当然这个是后话
以上就是订阅和发布的解释,当然,我可以实话告诉你,这个和我下面要贴的代码并没有什么关系。

一个气象监测应用的需求

概述:建立一个气象观测的应用,从气象站获取数据,并实时更新三个布告板:目前状况,天气统计,天气预报
目前状况:温度,湿度,气压
天气统计:平均温度,最低温度,最高温度
天气预报:明天下雨吗?
以下是具体实现,涉及到的设计原则:
1,针对抽象编程,不针对实现编程
2,多用组合少用继承
3,开闭原则,对扩展开放,对修改关闭(所有设计模式都是围绕这个终极目标来对不同场景进行设计的)
  1. package observer;
  2. /**
  3. * 观察主题
  4. */
  5. public interface Observable{
  6. public void addObserver(Observer observer);//添加观察者
  7. public void removeObserver(Observer observer);//移除观察者
  8. public void notifyObservers(WeatherData data);//通知所有观察者
  9. }

观察主题也可以是抽象类,具体可以参考java.util.Observable,这里就不展开来bb了。

那么什么时候选择抽象类,什么时候选择接口呢?

我的理解是,这个就要看,代价高不高了,抽象类的好处就是,可以少写代码,实现复用。接口可以应对各种不同的变化,因为观察者并不一定有共同使用的实现类,可能它们完全就是不同的东西。

这个地方我只有一个主题,我想用什么就用什么,就是这么任性,如果以后出了第二个或者第三个主题,那么就可以考虑具体是抽象类还是接口了,当然也不排除两种都用的情况。当然这是后话,程序本来就是不断变化的,适当的时候用适当的设计才是王道。

  1. package observer;
  2. /**
  3. * 观察者
  4. */
  5. public interface Observer {
  6. public abstract void update(WeatherData data);
  7. }
  1. package observer;
  2. /**
  3. * 天气数据
  4. */
  5. public class WeatherData {
  6. private float temperature;
  7. private float humidity;
  8. private float pressure;
  9. public WeatherData(){
  10.  
  11. }
  12. public WeatherData(float temperature, float humidity, float pressure){
  13. this.temperature = temperature;
  14. this.humidity = humidity;
  15. this.pressure = pressure;
  16. }
  17. }

getter,setter省略,不然篇幅太长了

在Observer这个接口里面,我的update()方法里面带了一个 WeatherData对象,为什么不是Object呢,为什么不是直接三个参数,温度,湿度,气压呢?我当时想了很久(我也是一边看书,一遍写的,不是把所有的都学完了才来写博客的,所以不懂的多了去了)

用Object的好处当然是不仅更新天气可以用这个,以后更新其他什么的也可以用,而且,如果我update方法里面的需求有了新的变化,比如,我要更新时间,那么,这个参数总不能放在WeatherData里面吧,好不符合逻辑啊,强迫症怎么受得了???

思来想去我的理解是这样的:

1,这个是一个气象站应用,那么应该就是存气象数据吧,这个总没问题嘛

2,写一个Object会不会被后面接替我工作的同事砍死啊,Object里面是什么,通过方法名根本就看不出来,必须去具体的代码里面看,万一我逻辑有点复杂,注释再少点,那他不就懵逼了?

3,不直接用三个参数也是为了以后的变化,万一变成5个参数咋办?改都改死人,而且要是5个都是float类型,好容易在传的时候传错,发现得早还好,万一传错了,但是又通过测试了怎么办呢?

肯定有人觉得这不可能,那我举个例子

接口的定义是:

void test(int a, int b, int c);

实现是 void test(int a, int c, int b){...}

调用接口的时候,传入的是test.test(a, c, b);那么请问,这个能通过测试吗?可以的。后面的人会掉坑里吗?我觉得他只要去改代码,那么就很有可能,嘿嘿嘿

  1. package observer;
  2. /**
  3. * 显示
  4. */
  5. public interface DisplayElement {
  6. void display();
  7. }

这个接口没什么解释的,就是显示用的,下面我要大段贴代码了

  1. package observer;
  2. /**
  3. * 当前天气状况*/
  4. public class CurrentConditionsDisplay implements DisplayElement, Observer {
  5. Observable observable;
  6. public CurrentConditionsDisplay(Observable observable) {
  7. this.observable = observable;
  8. }
  9. WeatherData data = new WeatherData();
  10. @Override
  11. public void display() {
  12. System.out.println("当前的天气状况: " +
  13. "温度(℉):" + data.getTemperature() + " " +
  14. "湿度:" + data.getHumidity() + " " +
  15. "气压:" + data.getPressure());
  16. }
  17. @Override
  18. public void update(WeatherData data) {
  19. this.data = data;
  20. display();
  21. }
  22. }
  1. package observer;
  2. /**
  3. * 天气统计
  4. *
  5. */
  6. public class StatisticsDisplay implements DisplayElement, Observer {
  7.  
  8. private Float average;
  9. private Float highest;
  10. private Float lowest;
  11. @Override
  12. public void display() {
  13. System.out.println("天气统计:" +
  14. "平均温度(℉):" + average + " " +
  15. "最高温度(℉):" + highest + " " +
  16. "最低温度(℉):" + lowest);
  17. }
  18. @Override
  19. public void update(WeatherData data) {
  20. float temperature = data.getTemperature();
  21. average = null == average ? temperature :
  22. (average + temperature) / 2;
  23. highest = null == highest ? temperature :
  24. (highest > temperature ? highest : temperature);
  25. lowest = null == lowest ? temperature :
  26. (lowest > temperature ? temperature : lowest);
  27. display();
  28. }
  29.  
  30. }
  1. package observer;
  2. /**
  3. * 天气预报*/
  4. public class ForecastDisplay implements DisplayElement, Observer {
  5. private float pressure;
  6. @Override
  7. public void update(WeatherData data) {
  8. pressure = data.getPressure();
  9. display();
  10. }
  11. @Override
  12. public void display() {
  13. System.out.println("天气预测:" + forecast());
  14. }
  15. private static final float INFRABAR = 28.5f;//气压低就会下雨
  16. private String forecast(){
  17. return INFRABAR < pressure ? "明天不下雨" : "明天要下雨";
  18. }
  19. }
  1. package observer;

  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /**
  5. * 天气主题
  6. *
  7. */
  8. public class Weather implements Observable {
  9. private List<Observer> observers = new ArrayList<>();;
  10. public void addObserver(Observer observer) {
  11. observers.add(observer);
  12. }
  13. public void removeObserver(Observer observer) {
  14. observers.remove(observer);
  15. }
  16. public void notifyObservers(WeatherData data) {
  17. for (Observer observer : observers)
  18. observer.update(data);
  19. }
  20. }
  1. package observer;
  2. /**
  3. * 测试
  4. * @author Skysea
  5. *
  6. */
  7. public class Client {
  8.  
  9. public static void main(String[] args) {
  10. Observable observable = new Weather();
  11. Observer currentCondition = new CurrentConditionsDisplay(observable);
  12. Observer statistics = new StatisticsDisplay();
  13. Observer forecast = new ForecastDisplay();
  14. observable.addObserver(currentCondition);
  15. observable.addObserver(statistics);
  16. observable.addObserver(forecast);
  17. observable.notifyObservers(new WeatherData(80, 65, 30.4f));
  18. observable.notifyObservers(new WeatherData(82, 70, 29.2f));
  19. observable.notifyObservers(new WeatherData(78, 90, 25.1f));
  20. }
  21. }

运行结果:

在测试代码中,添加观察者到主题这些代码,也是可以放在观察者中去实现的,在初始化的时候就把自己加入到主题的列表中

在实际的使用中,如使用Spring的依赖注入之类的,还是很好用的,也可以省很多事情,最后的交互方式大概是这个样子

  1. package observer;
  2. /**
  3. * 测试2
  4. */
  5. public class Client2 {
  6. @Autowired
  7. private Observable observable;
  8. public static void main(String[] args) {
  9. observable.notifyObservers(new WeatherData(80, 65, 30.4f));
  10. observable.notifyObservers(new WeatherData(82, 70, 29.2f));
  11. observable.notifyObservers(new WeatherData(78, 90, 25.1f));
  12. }
  13. }

嗯,没错,什么初始化都没有了,观察者在启动的时候就注入到主题中了,只需要调用主题的接口就好了,而主题也仅仅是持有了一个List<Observer> observers;具体的观察者是谁它也不知道,但是它们还是可以相互交互,是不是很酷?

好了,我bb完了,本系列的大部分所有例子,都是在看headfirst设计模式时,看到的,并加入自己的理解写下来的(当然我也编了一些),写这段话的目的也是为了避免一些不必要的误会,以后的每期我都会复制这段话,哈哈哈

headfirst设计模式(2)—观察者模式的更多相关文章

  1. HeadFirst设计模式之观察者模式

    一.什么是观察者模式 观察者模式定义了一系列对象间一对多的关系,当主题对象的状态发生变化时,会通知所有观察者 二.自定义观察模式 1. 2. package headfirst.designpatte ...

  2. Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---观察者模式之WeatherReport[转]

      1   2{<HeadFirst设计模式>之观察者模式 }   3{ 主题与观察者                    }   4{ 编译工具 :Delphi7.0          ...

  3. HeadFirst设计模式读书笔记--目录

    HeadFirst设计模式读书笔记(1)-策略模式(Strategy Pattern) HeadFirst设计模式读书笔记(2)-观察者模式(Observer Pattern) HeadFirst设计 ...

  4. 【HeadFirst设计模式——开篇】

    近期在看HeadFirst,接下来的一段时间会陆续更新有关HeadFirst设计模式相关的文章.记得非常久之前在学习大话设计模式的时候,仅仅是走马观花的大致走过一遍.至于里面非常多东西都掌握的不是非常 ...

  5. 【Head-First设计模式】C#版-学习笔记-开篇及文章目录

    原文地址:[Head-First设计模式]C#版-学习笔记-开篇及文章目录 最近一年断断续续的在看技术书,但是回想看的内容,就忘了书上讲的是什么东西了,为了记住那些看过的东西,最好的办法就是敲代码验证 ...

  6. 《HeadFirst设计模式》读后感——对学习设计模式的一些想法

    最近看完了<HeadFirst设计模式>,GOF的<设计模式——可复用面向对象软件的基础>的创建型模式也读完了,经历了从一无所知到茅塞顿开再到充满迷惑的过程. 不得不说< ...

  7. Headfirst设计模式的C++实现——策略模式(Strategy)

    前言 最近在学习<Headfirst设计模式>,里面的例子都是Java的.但是我对Java并不熟悉,所以试着用C++来实现书中的例子. 先来看看Duck以及子类 Duck.h #inclu ...

  8. 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)

    原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...

  9. 设计模式之观察者模式(Observable与Observer)

    设计模式之观察者模式(Observable与Observer) 好久没有写博客啦,之前看完了<设计模式之禅>也没有总结一下,现在回忆一下设计模式之观察者模式. 1.什么是观察者模式 简单情 ...

随机推荐

  1. RSA实践指南

    创建时间:2005-03-02 文章属性:原创 文章提交:watercloud (watercloud_at_xfocus.org) RSA算法基础->实践 讲讲自己学习RSA中的实践过程,已经 ...

  2. linux动态链接库---一篇讲尽

    一般我们在Linux下执行某些外部程序的时候可能会提示找不到共享库的错误, 比如: tmux: error while loading shared libraries: libevent-1.4.s ...

  3. 12、手把手教你Extjs5(十二)执行菜单命令在tabPanel中显示模块

    上面设计好了一个模块的主界面,下面通过菜单命令的执行来把这个模块加入到主界面当中.在MainModule.js中有一个函数,生成了当前的菜单数据: // 根据data.systemMenu生成菜单条和 ...

  4. LPC1788做U盘的时候对命令的响应

    首先是对于端点的数据处理 #ifndef __USBEP2_H_ #define __USBEP2_H_ #include "usb.h" #include "usbhw ...

  5. linux下JUCE源码编译依赖库

    JUCE 源码https://github.com/julianstorer/JUCE 想在ubuntu下编译需要提前安装以下依赖库 sudo apt-get install mesa-common- ...

  6. PHP实反向代理-收藏

    需求 现在有些后辍的域名不支持备案,这个时候需要用免备案主机或空间做个反向代理,这样可实现内容存放在国内主机统一管理 实现 用 php-dynamic-mirror 可实现,并在头部进行域名转换,可实 ...

  7. github的SSH配置如下

    Git是分布式的代码管理工具,远程的代码管理是基于SSH的,所以要使用远程的Git则需要SSH的配置. github的SSH配置如下: 一 . 设置Git的user name和email: $ git ...

  8. 集群下Cookie共享,必须要设置machineKey

    这个节允许你设置用于加密数据和创建数字签名的服务器特定的密钥.ASP.NET自动使用它来保护表单验证Cookie,你也可以将它用于受保护的视图状态数据.同时,这个密钥还用于验证进程外的会话状态提供程序 ...

  9. iOS 导航栏不可点击

    self.navigationController.navigationBar.userInteractionEnabled = NO;

  10. JavaScript定时机制、以及浏览器渲染机制 浅谈

    昨晚,朋友拿了一道题问我: a.onclick = function(){ setTimeout(function() { //do something ... },0); }; JavaScript ...