Java设计模式——观察者模式

Java设计模式之观察者模式

前面一篇文章提到了策略模式,提出了三个设计原则,这里提出第四个设计原则

设计原则:

  • 为了交互对象之间的松耦合的设计而努力。

针对报社:出版者+订阅者=观察者模式

报社是获取信息的主题对象,当报社获取到的信息更新了,报社将把信息传送给它的订阅者;当然订阅者也可以通知报社停止订阅,报社就把该对象从订阅者列表中去除,他不再能接收到报社传送的信息。

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

观察者模式有两个部分:

  • 观察者
  • 主题(也叫被观察者)

一个主题对象关联了多个观察者,当主体对象有数据更新或者状态改变时,它就可以通知它所关联的观察者们;就如果狗对象、猫对象、老鼠对象已经注册成为观察者,他们就将在主体对象数据更新时接收到主题对象发送的通知(通知数据更新了,你可以通过pull或者push的方式获取),这也就是java.util包下的ObserverObservable实现的观察者模式

我们通过Head FIrst 设计模式中的场景实现观察者模式

场景:一个气象站由WeatherData类或者气象数据温度、湿度、气压,作为主题类,由多个公告板(展示效果不同,数据都是这三个)作为观察者类,当气象数据更新或者改变时,该类将通知公告板更新展示的数据。

WeatherData该主题需要能添加观察者删除观察者通知观察者更新数据,并且要获取气象局发布的数据

我们把主体对象写一个接口如下,不同主题实现不同功能只需实现该接口

public interface Subject {
    //添加观察者
    void registerObserver(Observer o);
    //删除
    void removeObserver(Observer o);
    //通知
    void notifyObservers(List<Observer> o);
    void setChanged();
    boolean getChanged();
}

其中setChanged方法把数据是否更新的状态的标志设置为True,在更新状态或者数据时执行该方法

getChanged()获取该标志

WeatherData只需实现该接口,添加获取气象数据的方法即可

public class WeatherData implements Subject{
    private WeatherArgs args = new WeatherArgs();
    private List<Observer> obs = new ArrayList<>();
    private boolean changed = false;
    //设置数据
    void setWeatherData(String template,String humidity,String pressure){
        args.setTemplate(template);
        args.setHumidity(humidity);
        args.setPressure(pressure);
        //更新完成后设置标志为true
        setChanged();
        //调用更新后通知观察者方法
        onWeatherDataChanged();
    }
    //数据更新时通知观察者 push数据给观察者
    private void onWeatherDataChanged(){
        if(getChanged()){
            notifyObservers(obs);
        }
    }
    //观察者注册
    public void registerObserver(Observer o) {
        if(o==null){
            throw new RuntimeException();
        }
        obs.add(o);
    }
    //观察者移除
    public void removeObserver(Observer o) {
        obs.remove(o);
    }
    //通知观察者们数据更新 发送数据给观察者们
    public void notifyObservers(List<Observer> obs) {
        obs.forEach((o)->o.update(this,args));
    }
    @Override
    public void setChanged() {
        changed = true;
    }
    @Override
    public boolean getChanged() {
        return changed;
    }
}

其中WeatherArg为气象数据参数类,当我们不使用这个类时,我们需要每次更新时传入特定的值,下面这样

update(String template,String humidity,String pressure);
update(100,200,200kpa);

这样需要更改数据种类和数量时,需要修改很多代码,所以我们把它封装起来

@Data
class WeatherArgs {
    //温度
    private String template;
    //湿度
    private String humidity;
    //气压
    private String pressure;
}

添加这个类是为了如果需要更改数据类型和数量时只需更改这个类就行

我们用List存放观察者,onWeatherDataChanged方法检测是否更新,如果更新就通知观察者

观察者有多个,其中都有update方法执行被通知时更新数据,通过不同的display方法展示不同效果的气象数据,我们编写两个接口

  • 观察者接口 (有多个观察者)Observer
  • 展示数据接口(有不同展示方法) Displayable
public interface Observer {
    /**
     * 更新数据
     * @param subject 主题对象
     * @param args 更新参数
     */
    void update(Subject subject,Object args);
}
public interface Displayable {
    /**
     * 数据展示方法
     */
    void display();
}

下面我们实现一个观察者

它需要实现Observer和`Displayable接口实现update和display方法

我们在构造方法传入一个主题对象,告诉他我们要订阅他

public class CurrentConditionDisplay implements Observer,Displayable{
    //可用于观察者对主题对象取消订阅
    private Subject subject;
    private WeatherArgs weatherArgs;
    //创建时注册
    public CurrentConditionDisplay(Subject s){
        s.registerObserver(this);
        subject = s;
    }
    @Override
    public void update(Subject subject, Object args) {
        if(subject instanceof WeatherData){
            weatherArgs = (WeatherArgs) args;
            display();
        }
    }
    @Override
    public void display() {
        System.out.println("template:"+weatherArgs.getTemplate()+", humidity:"+weatherArgs.getHumidity()+", pressure:"+weatherArgs.getPressure());
    }
    //观察者取消订阅 通知主题对象删除我
    private void removeMe(){
        subject.removeObserver(this);
    }
}

至此,观察者模式就完成了

写一个测试类:

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        weatherData.setWeatherData("100","90","100KPa");
    }
}
output:
template:100, humidity:90, pressure:100KPa

大家可以看一下JDK中内置的Observer和Observable实现的观察者模式,大体实现是一样的

JDK内置观察者模式

Observable被观察者

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
        notifyObservers(null);
    }
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    protected synchronized void setChanged() {
        changed = true;
    }
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
    public synchronized int countObservers() {
        return obs.size();
    }
}

Observer

public interface Observer {
    //o为被观察者主题,args为更新的数据
    void update(Observable o, Object arg);
}

但是我们可以看到该Observable是具体的实现类,不符合设计模式中的

  • 多用组合少用继承

不方便我们继承其他类

有两个办法 :

  • 使用上述我们自己实现的观察者模式
  • 扩展Observable类

代码地址:Github观察者模式


Java设计模式|策略模式

Java设计模式|观察者模式

Java设计模式|装饰者模式

Java设计模式|工厂模式

Java设计模式|命令模式

Java设计模式|适配器模式和外观模式

Java设计模式|模板方法模式

Java设计模式|迭代器模式和组合模式

Java设计模式|状态模式

Java设计模式|代理模式

Java设计模式|单例模式

Java设计模式|备忘录模式

Java设计模式|访问者模式

Java设计模式|复合模式

Java设计模式|桥接模式

Java设计模式|生成器模式

Java设计模式|享元模式/蝇量模式

Java设计模式|原型模式

Java设计模式|责任链模式

Java设计模式|中介者模式

  • 本文作者: dzou | 微信:17856530567
  • 本文链接: http://www.dzou.top/post/261ed4ab.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
  • 并保留本声明和上方二维码。感谢您的阅读和支持!