观察者模式是一个常用模式:有一个主题(subject),有很多观察者(observers)注册了此主题。那么此主题一旦发布新的数据,这些注册了此主题的观察者就能够察觉这些数据的更新,根据这些更新的数据进行各自的处理。
就像订报纸一样。报社(subject)一旦发布新的报纸,他就主动把这些新的报纸发送给订户们(observers)。而不是订户是不是去报社看看有新报纸没~
下面看看这个类图:
这是一个天气预报的类图,气象站(WeatherData)一旦发布新的天气数据,它的订户们(各种形式的公告板,如StatisticDisplay,ForecastDisplay等)就能收到最新的天气数据,进行更新(P.S 不用看那个DisplayElement接口,与此模式无关,它只是为了方便各个公告板不同的显示而已)。
我们看看调用的过程:
//创建一个关于天气数据的主题,注意这里可以不要 //Subject subject = new WeatherData() //因为后面(最后一句)要用到此天气预报站来改变数据。或者强转一下也行吧~ WeatherData wd = new WeatherData(); //创建一个观察者,顺便也注册了这个主题 Observer ob1 = new CurrentConditionsDisplay(wd); //再创建多一个观察者,表示一下可以许多观察者一起订阅一个主题 Observer ob2 = new CurrentConditionsDisplay(wd); wd.setData(...);//改变一些数据,这个方法会调用measurementsChanged()等。这样,注册者都会受到改变的信息。
详细的Java代码如下:
View Code
import java.util.ArrayList; import java.util.List; public class ObserverPattern { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ConcreteSubject cs = new ConcreteSubject(); ConcreteObserver1 co1 = new ConcreteObserver1(cs); ConcreteObserver2 co2 = new ConcreteObserver2(cs); cs.setData(30.4, 34.3); } } interface Subject{ //List<Observer> observers = null; void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } interface Observer{ void update(double temperature, double humidity); } class ConcreteSubject implements Subject{ List<Observer> observers; private double temperature; private double humidity; public ConcreteSubject() { // TODO Auto-generated constructor stub observers = new ArrayList<Observer>(); } @Override public void registerObserver(Observer o) { // TODO Auto-generated method stub observers.add(o); } @Override public void removeObserver(Observer o) { // TODO Auto-generated method stub if(observers.contains(o)){ observers.remove(o); } } @Override public void notifyObservers() { // TODO Auto-generated method stub for(Observer o: observers){ o.update(this.temperature,this.humidity); } } //设置温度湿度以改变状态 public void setData(double d, double e){ this.temperature = d; this.humidity = e; notifyObservers(); } } class ConcreteObserver1 implements Observer{ private double temperature; private double humidity; private Subject subject; public ConcreteObserver1(Subject c) { // TODO Auto-generated constructor stub subject = c; subject.registerObserver(this); } @Override public void update(double t, double h) { // TODO Auto-generated method stub this.temperature = t; this.humidity = h; print(); } public void print(){ System.out.println("In ConcreteObserver1:"); System.out.println("Temperature = "+this.temperature); System.out.println("Humidity = "+this.humidity+"\n"); } } class ConcreteObserver2 implements Observer{ private double temperature; private double humidity; private Subject subject; public ConcreteObserver2(Subject c) { // TODO Auto-generated constructor stub subject = c; subject.registerObserver(this); } @Override public void update(double t, double h) { // TODO Auto-generated method stub this.temperature = t; this.humidity = h; print(); } public void print(){ System.out.println("And in ConcreteObserver2:"); System.out.println("Temperature is "+this.temperature+"!!!"); System.out.println("Humidity is "+this.humidity+"~~\n"); } }
实际上,一旦数据变化,主题就会调用观察者的方法。本质就是一个类似回调函数的作用。
说到回调,突然想起最近看的相关内容,来分享分享。
请看:
#include <stdio.h> int print(int a){ printf("in print(), a=%d, and I will return a\n",a); return a; } int print2(int a){ printf("in print2(), a=%d, and I will return a\n",a); return a; } //传入一个4字节的整型数,它的意义是一个函数的首地址 void callback1(int addr, int b){ if(b>10){ //把这个4字节的整型地址,强制转化为一个函数指针,并调用这个函数 ((int (*)(int))addr)(b); }else{ printf("No callback invoked!\n"); } return; } //传入一个函数指针 void callback2(int (*fp)(int), int b){ if(b>10){ //直接用这个函数指针 //也可以这样调用: (*fp)(b); fp(b); }else{ printf("No callback invoked!\n"); } return; } int main(){ //传入一个函数地址,强制转化为4字节的int值 callback1((int)print,99); //传入函数地址。注意:函数名就是函数的首地址,它是个4字节的整型值,不是变量 callback2(print,99); //传入函数指针。它和print的值是一样的,但它的类型是函数指针。通常来说,使用这种方法实现回调。 callback2(&print,99); return 0; }
顺便又贴一贴《C陷阱与缺陷》那个函数指针的例子,来帮助理解函数指针:
//我要在开机的时候,在地址为0的地方调用void fun(void)这个函数 //于是作者写下了这个东西 (*((void (*)(void))0))(); //首先,下面两个通过函数指针来调用函数是一样的 fp(); (*fp)(); //其次,怎么写一个函数指针fp呢 //函数指针只关心:1,函数参数列表;2,函数返回值 int fun(int);//这是一个函数声明,参数列表为一个int,返回值为一个int int (*fp)(int);//这是一个函数指针声明,参数列表为一个int,返回值为一个int (int (*)(int)) //这是一个函数指针的类型。把函数指针声明的名字fp和分号去掉,再加个括号就好.注意:我没加分号,因为它是个类型,是个代码片段,so.. (int (*)(int))0; //把0这个地址(或者就是一个整型数)强制转为传入int,返回int的函数的首地址 ((int (*)(int))0)(); //以简洁的方式调用这个函数。 (*((int (*)(int))0))(); //更上一行一样的效果,不过外形更唬人~
-------------
图来自《Java Design Pattern》