以下为观察者模式详解:

引子:

假设有这样一个问题,有一条河经过一个山谷,山谷下有一个村庄,人们在山谷处修建了一个水库,并安排专人管理,当水库的水位过高时要通知下游居民注意水库的开闸放水,当水库的水温过低时要通知到水库游泳时要注意安全,那么现在我们要用OO思想用代码来设计这样一件事情,该怎么做?

首先我们想像一下我们订阅报纸或杂志的过程,先向报社订阅报纸,订阅后只要这家报社还在运营就会按时把报纸送到你家,当你不想再看这家的报纸时只需要取消订阅,以后就不会再送报纸给你了。

在这样一件事情中读者扮演的是一个观察者的角色,不同的读者同时接收一份报纸查看新闻,他们对新闻的关心点不一。而报社提供新闻的更新,这就是观察者模式,也就是出版者+订阅者。注意:收到报纸的人不一定就只自己看他还可以把报纸发送给其他人,一个人身兼多个角色。

我们可以把出版者叫做主题,读者叫做观察者,这是一种一对多的关系,观察者依赖于此主题,只要主题的状态一有变化,就会通知给观察者,而观察者要想收到通知就要先向主题注册,不想收到时再注销。那么观察者模式是怎么设计的呢?先看这个观察者模式的类图:

先定义两个接口,Subject和Observer,Subject中有3个方法分别为,注册(调用注册方法时把观察者加入到列表中),删除(把观察者从列表中删除)和通知(遍历列表逐个发送更新),Observer接口中有一个更新方法,具体主题类ConcreteSubject实现了Subject接口,具体观察者类ConcreteObserver实现了Observer接口。当观察者通过主题 的registerObserver()方法向主题注册了之后,主题一有更新就会通过notifyObserver()方法来调用观察者的update()方法来把通知发送到位,观察者通过主题的removeObserver()来取消注册,观察者收到变更通知后分析变更的数据,进行相应的处理。

观察者模式的原则---松耦合:主题只知道观察者实现了某个接口(Observer接口),主题不需要知道观察者的具体类是谁做了些什么等。可以随时向主题中注册新的观察者,主题唯一依赖的是一个实现了Observer接口的对象的列表,注册添加的对象都存在这个列表中,在运行中可以用新的观察者取代已有的观察者或删除已有的观察者主题 都不会受到任何影响。不关心观察者的类型,不管加入什么样的观察者主题的代码不需要修改。可以独立地复用主题或观察者(在其他地方使用),改变主题或观察者中的一方,不会影响另一方。

现在我们开始用观察者模式来设计水库问题:

先定义两个接口:

/**
* @author Homg
*/
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver(Subject subject,Object datas);
} /**
*
* @author Homg
*
*/
public interface Observer {
public void update(Subject subject, Object datas);
}

当水库水位或水温有变量时主题要调用update方法来把数据发送给居民,那么也许你会想用

update(int waterLevel, int waterTemperature)来发送数据,但有没有想过要是以后不仅只通知这两个数据,还有别的,那这个方法的参数不是要长到天上去吗?这不科学,所以我们需要把参数封装起来。再者,如果这个观察者注册了多个主题,那么update时是不是需要辨别一下是哪个主题发来的更新?

封装数据的类:

/**
* 封装数据,方便操作
*
* @author Homg
*
*/
public class ReserviorData {
private int waterLevel;
private int waterTemperature; public ReserviorData() {
super();
} public ReserviorData(int waterLevel, int waterTemperature) {
this.waterLevel = waterLevel;
this.waterTemperature = waterTemperature;
} public int getWaterLevel() {
return waterLevel;
} public void setWaterLevel(int waterLevel) {
this.waterLevel = waterLevel;
} public int getWaterTemperature() {
return waterTemperature;
} public void setWaterTemperature(int waterTemperature) {
this.waterTemperature = waterTemperature;
} }

观察者居民A类与居民B类:

/**
*
* @author Homg
*
*/
public class ResidentA implements Observer {
// 水位
private int waterLevel;
// 水库管理者类的引用
private ReservoirManager reservoirManager; public ResidentA(ReservoirManager reservoirManager) {
this.reservoirManager = reservoirManager;
// 注册接收
reservoirManager.registerObserver(this);
} // 更新方法,用subject辨别主题 ,更新的数据在datas里,取出数据后调用display显示
@Override
public void update(Subject subject, Object datas) {
// 先辨别是哪个主题
if (subject instanceof ReservoirManager) {
ReserviorData reserviorData = (ReserviorData) datas;
this.waterLevel = reserviorData.getWaterLevel();
desplay();
}
} // 这是改为“拉”数据时要改变的方法
// @Override
// public void update(Subject subject, Object datas) {
// if (subject instanceof ReservoirManager) {
// this.waterLevel = ((ReservoirManager) subject).getWaterLevel();
// desplay();
// }
// } // 显示数据
public void desplay() {
System.out.println("我是居民A,水库的当前水位为:" + waterLevel + "米。");
} } /**
*
* @author Homg
*
*/
public class ResidentB implements Observer {
private int waterTemperature;
private ReservoirManager reservoirManager; public ResidentB(ReservoirManager reservoirManager) {
this.reservoirManager = reservoirManager;
// 注册接收
reservoirManager.registerObserver(this);
} @Override
public void update(Subject subject, Object datas) {
//先辨别是哪个主题
if (subject instanceof ReservoirManager) {
ReserviorData reserviorData = (ReserviorData) datas;
this.waterTemperature = reserviorData.getWaterTemperature();
desplay();
}
} // 这是改为“拉”数据时要改变的方法
// @Override
// public void update(Subject subject, Object datas) {
// if (subject instanceof ReservoirManager) {
// this.waterTemperature = ((ReservoirManager)
// subject).getWaterTemperature();
// desplay();
// }
// } public void desplay() {
System.out.println("我是居民B,水库的当前水温为:" + waterTemperature + "度。");
} }

再考虑一个问题,我们是否需要一个变量来控制是否发送通知?比如:我们不希望水温或水位有一点点变化就马上通知,这样的话会通知不段居民哪有多么多时间来处理这样琐碎的通知,还有将来可能要在多个地方进行判断是否发送更新。综上所述控制肯定需要,那么我们用一个boolean值来控制,并用set,cancel方法来改变其值,看下面的水库管理者主题类:

/**
*
* @author Homg
*
*/
public class ReservoirManager implements Subject {
// 存放注册者的list
private ArrayList<Observer> observerList;
// 数据类的引用
private ReserviorData reserviorData;
// 用来控制当水库信息数据发生变化时是否发送通知
private boolean isChange = false; public ReservoirManager() {
observerList = new ArrayList<Observer>();
reserviorData = new ReserviorData();
} // 用来注册观察者的方法
@Override
public void registerObserver(Observer o) {
observerList.add(o);
} // 用来注销观察者的方法
@Override
public void removeObserver(Observer o) {
int i = observerList.indexOf(o);
if (i >= 0) {
observerList.remove(i);
}
} // 该方法用来通知每一个注册的观察者
@Override
public void notifyObserver(Subject subject, Object datas) {
if (isChange) {
// 遍历观察者列表
for (int i = 0; i < observerList.size(); i++) {
Observer observer = observerList.get(i);
// 使观察者得到更新的数据
observer.update(subject, datas);
}
}
} // 设置水库信息数据
public void setReservoirData(ReserviorData reserviorData) {
this.reserviorData = reserviorData;
measurementsChanged();
} // 测量数据的更改
public void measurementsChanged() {
// 如果水位高于5或水温低于5度那么就发送通知
if (reserviorData.getWaterLevel() > 5
|| reserviorData.getWaterTemperature() < 5) {
setChanged();
}
notifyObserver(this, reserviorData);
// 这是要改成“拉”数据时要改变的代码
// notifyObserver(this, null);
} public void setChanged() {
isChange = true;
} public void cancelChanged() {
isChange = false;
} // 这是要改成“拉”数据时要添加的方法
// public int getWaterLevel() {
// return reserviorData.getWaterLevel();
// }
//
// public int getWaterTemperature() {
// return reserviorData.getWaterTemperature();
// } }

测试条件:

每隔一秒随机改变一次水位和水温值,并显示出时间(秒),代码如下:

/**
* @author Homg
*/
public class Test { /**
* @param args
*/
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(task, 0, 1000);
} private static TimerTask task = new TimerTask() {
int time = 0; @Override
public void run() {
System.out.println("时间为=" + time + "秒");
time++;
Random random = new Random();
int waterLevel = random.nextInt(15);
int waterTemp = random.nextInt(10);
ReserviorData reserviorData = new ReserviorData(waterLevel,
waterTemp);
setDatas(reserviorData);
}
}; public static void setDatas(ReserviorData reserviorData) {
ReservoirManager reservoirManager = new ReservoirManager();
ResidentA residentA = new ResidentA(reservoirManager);
ResidentB residentB = new ResidentB(reservoirManager); reservoirManager.setReservoirData(reserviorData);
} }

运行结果:

时间为=0秒
我是居民A,水库的当前水位为:8米。
我是居民B,水库的当前水温为:2度。
时间为=1秒
我是居民A,水库的当前水位为:3米。
我是居民B,水库的当前水温为:1度。
时间为=2秒
我是居民A,水库的当前水位为:1米。
我是居民B,水库的当前水温为:2度。
时间为=3秒
时间为=4秒
我是居民A,水库的当前水位为:12米。
我是居民B,水库的当前水温为:2度。
时间为=5秒
时间为=6秒
我是居民A,水库的当前水位为:12米。
我是居民B,水库的当前水温为:9度。
时间为=7秒
时间为=8秒
我是居民A,水库的当前水位为:7米。
我是居民B,水库的当前水温为:5度。
时间为=9秒
我是居民A,水库的当前水位为:14米。
我是居民B,水库的当前水温为:0度。
时间为=10秒

细看可以发现每秒的水位和水温都不相同,且居民都收到了通知,第3秒,第5秒,第7秒,第10秒时并没有发送通知,这是因为不符合水位高于5或水温低于5度的发送条件所以没有发送通知给居民。

也许你已经发现了这样一个问题:我们上面发送通知是主题“推”送给观察者的,也就是说以后不管加入了多少发送的数据,每个观察者都会收到所有的数据,且不说安全性,如果某个观察者他只想知道水位的变化,那他也要收到这一大堆的数据,这样不科学,所以我们可以把观察者模式改成“拉”数据的形式,再看上面各类中的代码,里面有改成拉数据的代码(注释的方法或变量,代码上面写明了替换或添加)。由此我们也发现了观察者模式的实现方法不只一种。

类图:

应用:

Java api中有提供观察者模式,我们可以去继承和实现,java.util.Observable(主题),java.util.Observer(观察者),注意Observable不是接口是一个类,所以要去继承它,这也导致了不方便,java是单继承的,所以你的类继承了它就不能再继承别的类了,也限制了它的复用性等。

Java中Button的监听,是不是观察者模式?

Android中的广播是不是呢?

总结:

观察者模式就是定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

完整代码下载地址(也可以留下邮箱发给你):

http://download.csdn.net/detail/homg92/6800021

OO之观察者模式的更多相关文章

  1. 学C#之设计模式系列笔记(2)观察者模式

    一.借鉴说明 1.<Head First Design Patterns>(中文名<深入浅出设计模式>) 2.维基百科,观察者模式,https://zh.wikipedia.o ...

  2. [Head First设计模式]山西面馆中的设计模式——观察者模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 引言 不知不自觉又将设计模式融入生活了,吃个饭也不得安生,也发现生活中的很多场景,都可以用设计模式来模拟.原来设计模式就在 ...

  3. Head First 设计模式之观察者模式(Observer Pattern)

    前言: 这一节开始学习观察者模式,开始讲之前会先像第一节那样通过一个应用场景来引入该模式.具体场景为:气象站提供了一个WeatherData对象,该对象可以追踪获取天气的温度.气压.湿度信息,Weat ...

  4. java设计模式(六)--观察者模式

    转载:设计模式(中文-文字版) 目录: 简单目标任务实现 观察者模式介绍 观察者模式代码实现 观察者模式是JDK中使用最多的模式之一,非常有用.我们也会一并介绍一对多关系,以及松耦合(对,没错,我们说 ...

  5. OO与设计模式的原则、目标

    OO与设计模式的原则.目标(转) 前两天,和一朋友聊到OO设计原则时,对设计模式有了更深的了解,在这里总结一下,与大家分享.OO(Object–Oriented )面向对象   OO方法(Object ...

  6. Head First设计模式-观察者模式

    一.整体代码 Subject.java public interface Subject { public void registerObserver(Observer o); public void ...

  7. head frist 设计模式学习之 JVM中的博物馆奇妙夜(观察者模式)

    博物馆奇妙夜! 博物馆奇妙夜!博物馆奇妙夜!重说三!!!JVM看了<博物馆奇妙夜>电影之后,决定在自己家里开一个博物馆!毕竟需要什么new一下就好,博物馆很快就开起来了,并且任命你为馆长( ...

  8. java_设计模式_观察者模式_Observer Pattern(2016-07-27)

    看了好几篇文章,最终还是觉得<Head First 设计模式>举得例子比较符合观察者模式. 观察者模式概述: 观察者模式有时被称作发布/订阅模式,它定义了一种一对多的依赖关系,让多个观察者 ...

  9. 设计模式一:关于C++写观察者模式的一些收获

    先贴上部分代码: #include "stdafx.h" #include<iostream> #include<string> #include<v ...

随机推荐

  1. ubuntu下安装Sublime Text并支持中文输入

    Sublime Text还是文本编辑器中比较不错的,就是他的文件对比有些差劲吧,还有中文输入需要打补丁,不知道开发者是怎么想的... 当然,这个软件是收费的,但是不买也能一直的使用,在我天朝就这点好处 ...

  2. ConcurrentHashMap和Hashtable区别

    Hashtable:synchronized是针对整张Hash表的,即每次锁住整张表让线程独占安全的背后是巨大的浪费 ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以 ...

  3. php 学习笔记

    //本文转自:http://www.cnblogs.com/ronghua/p/6002995.html //语法错误(syntax error)在语法分析阶段,源代码并未被执行,故不会有任何输出. ...

  4. Map集合的四种遍历方式

    很久以前写的代码,和上一个做比较吧!便于以后查看 import java.util.HashMap; import java.util.Iterator; import java.util.Map; ...

  5. MyBatis(3.2.3) - Cache

    Caching data that is loaded from the database is a common requirement for many applications to impro ...

  6. 第一回 认识Bootstrap

    Bootstrap 是Twitter推出的一个用于前端开发的开源工具包. Bootstrap是基于HTML5和CSS3开发的,它在jQuery的基础上进行了更为个性化和人性化的完善,形成一套自己独有的 ...

  7. Cocoa 框架为什么采用两阶段的方式来创建对象?

    对于之前一直使用C#语言的我来说,刚开始接触Objective-c来创建对象时很迷惑,为何创建对象一般情况下需要通过发送两个消息(调用两个方法)才能创建一个类实例对象(例如[[UIButton all ...

  8. Quartz.NET快速上手第一课(官网文档翻译)

    Quartz.NET快速上手第一课(官网文档翻译) 原文链接 在你使用调度者(scheduler)之前,你需要对它进行实例化(谁能猜到这呢?).在实例化scheduler时候,你需要使用ISchedu ...

  9. c#基础学习汇总----------base和this,new和virtual

    base和this是c#中的两访问关键字,目的是用于实现继承机制的访问操作,来满足对对象成员的访问,从而为多态机制提供更加灵活的处理方式. 在看<你必须知道的.Net>一书中有一个例子很好 ...

  10. template_1

    0: 模板是一些为多种类型而编写的函数和类,而且这些类型都没有指定.当使用模板的时候,只需要把所希望的类型作为一个(显示或隐示的)实参传递给模板.模板是语言本身所具有的特效,她完全支持类型检查和作用域 ...