读headFirst设计模式

上次学习了策略模式,这次来学习观察者模式。这次先把书上的例子学习一下,然后再自己写一个例子,看是否能做到举一反三(或者说触类旁通),不过要想真正的掌握还要多多思考和练习。

学习书上的例子

现在我们有一个任务,需要根据天气状况来发布不同的布告,开始有3个布告板:当前状况,气象统计,天气预报。像这样的:

 

现在有一个天气情况的类WeatherData,可以设置和获取温度temperature,湿度humidity和气压pressure数据。要在天气变化时通知布告板,布告板更新以显示不同的值。开始可能想要这样做

我们以后需要添加新的布告板或者删除布告板,这就要求系统具有弹性,如果是按照上图这样做,每添加或删除布告板都需要修改WeatherData类。好吧,既然这样,我想我们需要来使用观察者模式了。

 认识观察者模式

加入用户订阅了报纸,报社只要有新的报纸就会给用户送过去,当用户不想看报纸了,就取消订阅,报社就不会送新的报纸过去了。在这里就是一个观察者模式,不过需要改一个名字:报社称为“主题”,用户称为“观察者”。只要主题有更新就会通知观察者,观察者然后更新自身状态。想想天气的例子是不是很类似,所以我们把观察者模式运用在天气的例子中,于是在我们的天气的例子中,天气状况就是主题,布告板就是观察者,只要天气状况发生改变就会通知布告板,然后布告板收到通知并改变自身状态。

定义观察者模式

在真实世界中,你通常会看到观察者模式被定义成:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

观察者模式类图

通常观察者模式被设计成包含Subject和Observer接口。看一下类图

软件设计原则

观察者模式通过实现接口的方式实现针对接口编程(这也是一种设计原则,上次说过,这里的接口不一定是interface关键字关键字修饰的接口)建立更有弹性的系统,使对象间的互相依赖降到了最低,这就是所说的松耦合。所以这里的设计原则是:为了对象之间的松耦合而努力。

使用观察者模式来写天气的例子

观察者接口Observer:

1 /**
2  * 观察者中只有一个update方法
3  */
4 public interface Observer {
5     void update(float temp, float humidity, float pressure);
6 }
View Code

这里update的参数是温度等一个个具体的值,这样不太好,在后面换成对象会好些。

主题接口Subject:

/**
 * 主题接口
 */
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}
View Code

显示布告板内容的接口DisplayElement:

/**
 * 显示布告板内容的接口
 */
public interface DisplayElement {
    void display();
}
View Code

天气状况WeatherData类实现主题接口Subject,所以WeatherData就是一个主题,当天气发生改变时通知所有的观察者

具体的主题WeatherData类:

public class WeatherData implements Subject {
    private ArrayList<Observer> observers = new ArrayList<Observer>();//存储观察者
    private float temperature;    //温度
    private float humidity;        //湿度
    private float pressure;        //气压

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    //通知所有观察者
    @Override
    public void notifyObservers() {
        for (int i = 0, j = observers.size(); i < j; i++) {
            observers.get(i).update(temperature, humidity, pressure);
        }
    }
    
    //测量值发生改变
    private void measurementsChanged() {
        notifyObservers();
    }

    //设置测量值
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}
View Code

具体的观察者:当前状况CurrentConditionDisplay类

public class CurrentConditionDisplay implements Observer, DisplayElement {
    private float temperature;    //温度
    private float humidity;        //湿度
    private float pressure;        //气压
    private Subject weatherData;
    
    //创建观察者时传入主题对象,将自己注册成观察者
    public CurrentConditionDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        //将自己注册成观察者
        weatherData.registerObserver(this);
    }
    
    //移除观察者
    public void removeObserver() {
        weatherData.removeObserver(this);
    }

    @Override
    public void display() {
        System.out.println(toString());
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public String toString() {
        return "CurrentConditionDisplay [temperature=" + temperature
                + ", humidity=" + humidity + ", pressure=" + pressure + "]";
    }
}
View Code

这里怎么显示或利用这些数据可以自己设计

测试一下:

public class ObserverTest {
    public static void main(String[] args) {
        //创建一个具体的主题
        WeatherData weatherData = new WeatherData();
        //创建一个具体的观察者
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        weatherData.setMeasurements(23, 45, 1);
        weatherData.setMeasurements(28, 31, 1);
        weatherData.setMeasurements(36, 12, 1);
        currentConditionDisplay.removeObserver();//移除
        weatherData.setMeasurements(15, 21, 1);
    }
}
View Code

你会看到每当天气改变时布告板会收到通知并自动更新

再附加一个气象统计的类,其他的类和测试类自己写一下

/**
 * 获取平均值
 */
public class WeatherStatistics implements Observer, DisplayElement {
    private static final float[] tempArr = new float[2];    //分别存贮次数和总数
    private static final float[] humidityArr = new float[2];
    private static final float[] pressureArr = new float[2];
    
    private float temperature;
    private float humidity;
    private float pressure; 
    
    //创建对象自动注册为观察者
    public WeatherStatistics(Subject weatherData) {
        weatherData.registerObserver(this);
    }
    
    @Override
    public void display() {
        System.out.println(toString());
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        average(temp, humidity, pressure);
        display();
    }

    private void average(float temp, float humidity, float pressure) {
        tempArr[0] = tempArr[0] + 1;
        tempArr[1] = tempArr[1] + temp;
        humidityArr[0] = humidityArr[0] + 1;
        humidityArr[1] = humidityArr[1] + humidity;
        pressureArr[0] = pressureArr[0] + 1;
        pressureArr[1] = pressureArr[1] + pressure;
        
        this.temperature = tempArr[1] / tempArr[0];
        this.humidity = humidityArr[1] / humidityArr[0];
        this.pressure = pressureArr[1] / pressureArr[0];
    }

    @Override
    public String toString() {
        return "WeatherStatistics [temperature=" + temperature + ", humidity="
                + humidity + ", pressure=" + pressure + "]";
    }
}
View Code

这样我们就设计了自己的给观察者模式,其实jdk中已经内置了观察者模式,位于java.util包下,与我们自己设计的有些不同,主题是继承Observable类,这时可以称之为“可观察者”,里面也有添加删除观察者的方法。观察者实现Observer接口,可观察者要如何送出通知呢?需要下面的步骤:

推荐看一下源码,会更加清晰

现在用jdk内置的观察者模式修改一下天气的例子

WeatherData:

import java.util.Observable;
/**
 * Observable 可观察者,即以前的主题
 */
public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData() {}

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    //测量值发生改变时
    private void measurementsChanged() {
        setChanged();
        notifyObservers();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
View Code

CurrentConditionDisplay:

/**
 * 观察者,实现了观察者接口
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {
    private Observable observable;
    private float temperature;
    private float humidity;
    private float pressure;

    public CurrentConditionDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);//添加为可观察者中的观察者
    }

    @Override
    public void update(Observable o, Object arg) {
        WeatherData weatherData = (WeatherData) o;
        this.temperature = weatherData.getTemperature();
        this.humidity = weatherData.getHumidity();
        this.pressure = weatherData.getPressure();
        display();
    }

    @Override
    public void display() {
        System.out.println(toString());
    }

    @Override
    public String toString() {
        return "CurrentConditionDisplay [temperature=" + temperature
                + ", humidity=" + humidity + ", pressure=" + pressure + "]";
    }
}
View Code

这个和我们刚才自己设计的不同:把主题(可观察者)传给了观察者。

测试一下:

public class ObserverTest {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        weatherData.setMeasurements(12, 24, 1.3F);
        weatherData.setMeasurements(20, 28, 1F);
    }
}
View Code

附另外一个是否适合出行的布告板GoingDisplay:

public class GoingDisplay implements Observer, DisplayElement {
    private Observable observable;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public GoingDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);    //添加为观察者
    }

    @Override
    public void display() {
        boolean isFitGoing = fitGoing();
        System.out.println(isFitGoing ? "适宜出行" : "不适合外出");
    }

    private boolean fitGoing() {
        if (temperature > 15 && temperature < 30 && humidity >= 20 && pressure == 1)
            return true;
        return false;
    }

    @Override
    public void update(Observable o, Object arg) {
        WeatherData weatherData = (WeatherData) o;
        this.temperature = weatherData.getTemperature();
        this.humidity = weatherData.getHumidity();
        this.pressure = weatherData.getPressure();
        display();
    }
}
View Code

好了,到这里我们把书上关于观察者模式的内容学完了,关于jdk内置的观察者模式,还是建议看一下源码。好了,书上的例子是一个主题和多个观察者,现在我们自己来写多个主题和一个观察者的例子。比如一个用户订阅了一个科技网站,然后还关注了一个电视剧,如果网站有新的咨询,电视剧有更新都会通知这个用户,这就符合观察者模式了,我们来做一下。不用jdk内置的,自己设计。

Observer接口改一下,以主题作为参数

public interface Observer {
    void update(Subject subject);
}
View Code

Subject接口和DisplayElement接口和前面的一样

具体的主题:科技网站Website

/**
 * 一个网站, 有资讯和博客,如果你订阅了或关注了,当有更新时,你会收到更新
 */
public class Website implements Subject {
    private List<Observer> observerList = new ArrayList<Observer>();
    private static boolean changed = false;
    private String message;
    private String blog;

    @Override
    public boolean addObserver(Observer observer) {
        return observerList.add(observer);
    }

    @Override
    public boolean removeObserver(Observer observer) {
        return observerList.remove(observer);
    }

    @Override
    public void notifyObservers() {
        if (observerList != null && observerList.size() > 0) {
            for (Observer observer : observerList) {
                observer.update(this);
            }
        }
    }
    
    //借鉴jdk内置观察者模式设置一个标记,一边选择性的通知观察者
    public void setChanged() {
        changed = true;
    }
    
    //内容更新时通知观察者
    public void setContent(String message, String blog) {
        this.message = message;
        this.blog = blog;
        if (changed)
            notifyObservers();
        changed = false;
    }

    @Override
    public String toString() {
        return "您订阅的网站有更新: Website [message=" + message + ", blog=" + blog + "]";
    }
}
View Code

类似Website的电视剧TV:

/**
 * 电视剧作为一个主题,当有更新时通知用户
 */
public class TV implements Subject {
    private List<Observer> observerList = new ArrayList<Observer>();
    private static boolean changed = false;
    private Date updateDate;//更新日期
    private Integer episodeNum;//更新到了哪一集

    @Override
    public boolean addObserver(Observer observer) {
        return observerList.add(observer);
    }

    @Override
    public boolean removeObserver(Observer observer) {
        return observerList.remove(observer);
    }

    @Override
    public void notifyObservers() {
        if (observerList != null && observerList.size() > 0) {
            //通知所有观察者
            for (Observer observer : observerList) {
                observer.update(this);
            }
        }
    }
    
    public void setChanged() {
        changed = true;
    }
    
    //剧集更新时通知所有观察者
    /**
     * 什么时候更新到了第多少集
     * @param updateDate
     * @param episodeNum
     */
    public void episodeUpdate(Date updateDate, Integer episodeNum) {
        this.updateDate = updateDate;
        this.episodeNum = episodeNum;
        if (changed) 
            notifyObservers();
        changed = false;
    }

    @Override
    public String toString() {
        return "您关注的电视剧更新啦,去看吧-=TV [updateDate=" + new SimpleDateFormat("yyyy-MM-dd").format(updateDate) + ", episodeNum=" + episodeNum
                + "]";
    }
}
View Code

具体的观察者User:

/**
 * 具体的观察者日,这个没有让其创建时自动注册为观察者,而是调用方法成为观察者,模拟用户自主订阅或关注
 */
public class User implements Observer, Display {
    private Subject subject;

    public User(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void display() {
        if (subject instanceof Website) {
            Website w = (Website) subject;
            System.out.println(w.toString());
        } else if (subject instanceof TV) {
            TV tv = (TV) subject;
            System.out.println(tv.toString());
        }
    }

    @Override
    public void update(Subject subject) {
        //主题把自身当作参数传过来 
        this.subject = subject;
        display();
    }
    
    //订阅或关注,这样自己就成了观察者
    public boolean subscribe() {
        return subject.addObserver(this);
    }

    //取消订阅或关注,这样自己从观察者中删除了
    public boolean unSubscribe() {
        return subject.removeObserver(this);
    }
}
View Code

测试一下:

/**
 * 试一下注释掉的代码
 */
public class ObserverTest {
    public static void main(String[] args) {
        run1();
    }
    
    public static void run1() {
        Website website = new Website();
        TV tv = new TV();
        User user_website = new User(website);
        User user_tv = new User(tv);
        //订阅,使自己成为观察者
        user_website.subscribe();
        user_tv.subscribe();
//        user_website.unSubscribe();//取消订阅
        
        website.setChanged();
        website.setContent("new message1", "new blog1");
        
//        tv.setChanged();
        tv.episodeUpdate(new Date(), 26);
    }
}
View Code

好了,这次讲解就这么多了,大家(当然,包括我)要想掌握观察者模式,还需要多多思考练习。

原文地址:https://www.cnblogs.com/pdzbokey/p/6489426.html