俊介三

一天更新一点,一天积累一点

导航

Observer Pattern

Posted on 2013-03-22 12:29  俊介三在前进  阅读(167)  评论(0编辑  收藏  举报

观察者模式是一个常用模式:有一个主题(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》