Java设计模式之观察者模式(Observer)以及PropertyChangeSupport与PropertyChangeListener的使用方法教程

本文的观察者模式实例 是使用java.beans库里提供的PropertyChangeSupport与PropertyChangeListener编写,想直接查看PropertyChangeSupport与PropertyChangeListener的基本介绍和使用方法的,请直接翻到第二点查看。笔者现在本科初学软件设计模式,如有不对之处,请广大读者评论区赏脸指正,本人马上更新修改文章。如果觉得还可以,那就点一个赞,非常感谢!笔者希望和大家共同进步。

一、观察者模式(Observer Pattern)

1、模式动机与模式意图

(1)模式动机

* 将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使得各个类紧密耦合,导致可重用性的降低。

* 观察者模式使得任意数目的观察者不必知道彼此的存在,且主题发生变化时都可以得到主题的通知,而同步改变状态。是一种很松的耦合。具有很好的可重用性。

(2)模式意图

定义对象间的一种一对多的依赖关系。当一方的对象改变状态时,所有的依赖者都会得到通知并被自动更新。

2、观察者模式的定义

观察者模式定义了对象间的一对多依赖关系。当一方的对象改变状态时,所有的依赖者都会被通知并自动被更新。

在观察者模式中,被依赖的一方叫做目标或主题(Subject),依赖方叫做观察者(Observers)。

观察者模式一种更好理解的名字是:发布-订阅模式(Publish-Subscribe)

主题与观察者之间的关系如下所示:

对抽象编程,而不是对实现编程。引入高级接口(把相同的东西抽象出来)

 

3、高质量设计的原则---松耦合(loose Coupling)

* 如果两个对象是松耦合的,则他们可以相互作用,但彼此的依赖性很小。

* 观察者模式符合松耦合的原则。因为:

    * 主题(subject)只需要知道其观察者(Observer)实现了某个接口。

    * 可以随时加入观察者。

    * 不需要修改主题就可以加入新的类型的观察者

    * 主题和观察者都可以独立地被复用

    * 修改主题或观察者都不会影响另一方。

    * 观察者之间互不相干。

4、在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。(JAVA9已弃用,在此只是简略介绍,本文重点介绍的PropertyChangeSupport和PropertyChangeListener在下方第二点

public interface Observer {

//第一参数是被观察者(主题)对象,第二个参数是方法所需要的参数

void update(Observable o, Object arg);

}

被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。以下是Observable类的方法列表

Observable()

addObserver(Observer o)

deleteObserver (Observer o)

notifyObservers()

notifyObservers(Object arg)

deleteObservers

setChanged()

clearChanged()

hasChanged()

countObservers()

类图如下:

 

5、观察者模式的优缺点

(1)优点

* 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。

* 观察者模式在观察目标和观察者之间建立一个抽象的耦合。

* 观察者模式支持广播通信。

* 观察者模式符合“开闭原则”的要求。

(2)缺点

* 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

* 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

* 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

 

6、Observer模式的适用性

(1)当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时,将这两者封装在独立的对象中使他们可以独立的改变和复用。

(2)Observer模式的运用场合:

    * 一个对象的改变需要同时改变其他对象,但不知道具体有多少其它对象需要改变。

    * 一个对象必须通知其他对象,而他又不能预先知道其它对象是谁。

 

7、小结:三大模式中的行为模式

* 意图:给出一种提供灵活行为的方式

* 基本思想:分离变化、并单独封装变化

* 模式实例:Strategy,Observer

* Observer模式属于行为型。不少人认为Observer模式是“解耦型模式”(降低对象间的耦合度)的最佳范例。

二、Java提供的观察者模式类的解析
java.util库里面的Observable类和Observer接口(Java9弃用了,本文之前的内容有简单介绍该类)
代替方案:
java.beans库里面的PropertyChangeEvent类和PropertyChangeSupport类,以及PropertyChangeListener接口
下面是对这些类的简单介绍:
 
(1)java.beans.PropertyChangeListener (interface)  
有这个接口就是观察者。
PropertyChangeListener一般作为被监听者的一个属性。
这个接口就一个方法:
​//当绑定属性发生更改时,将自动调用此方法。
/**
* This method gets called when a bound property is changed.
* @param evt A PropertyChangeEvent object describing the event source
*          and the property that has changed.
*/
void propertyChange(PropertyChangeEvent evt);

 

作用:接受一个event(被监听者状态改变自动产生的PropertyChangeEvent),然后根据这个event做点反应。

 
(2)java.beans.PropertyChangeSupport  (class),之后简称Support类
这个类用在 被观察者的类里,用来保存注册的观察者,并负责向他们提供被观察者的变化信息。
主要的方法如下:
构造函数:参数就是被监听者本身
public PropertyChangeSupport(Object sourceBean)

 

构造函数的一般使用方法如下:

private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

 

被监听者实现了上面的语句以后,pcs就代表被监听者本身
被监听者pcs中可以拥有一群监听者,这群监听者需要用如下Support类方法来添加:
public void addPropertyChangeListener(PropertyChangeListener listener)

 

使用方法如下:
public void addPropertyChangeListener(PropertyChangeListener listener){
    this.pcs.addPropertyChangeListener(listener);
}

 

有加就有减,下面是Support类删减监听者的方法:
public void removePropertyChangeListener(PropertyChangeListener listener)

 

使用方法如下:
public void firePropertyChange(String propName, Object oldValue, Object newValue) {
    this.pcs.firePropertyChange(propName, oldValue, newValue);
}

 

被监听者有变化了就该通知监听者,如何通知?请使用如下通知方法的一个:
下列方法 一般使用在被监听者set和get函数中
public void firePropertyChange(String propertyName, Object oldValue, Object newValue)
public void firePropertyChange(String propertyName, int oldValue, int newValue)
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue)
public void firePropertyChange(PropertyChangeEvent event)

 

实际上,前三个方法的参数都会封装成PropertyChangeEvent,然后调用最后一个方法。
不过在实际中,我们还是喜欢直接调用前三个方法中的一个,封装的事我们就不管了。前三个方法的参数都是三个,其中的oldValue和newValue就是改变前后的值,第一个就是给改变一个名字,好让监听者们根据这个名子来做响应。就像开会,政府的所有信息都会被记者听到,但是有的记者只对台湾问题感兴趣,而有的记者对中日问题感兴趣。
注意,PropertyChangeSupport既然被用到了被观察者的类(一般是一个bean)里,那么他的这些方法就只在被观察这里调用。
当这些方法被调用时会自动生成一个事件,然后根据事件名 propertyName 把事件添加到一个PropertyChange Event类中,然后自动调用监听者中的 propertyChange(PropertyChangeEvent evt)方法,在 propertyChange方法中,可以通过 Event 对象的方法,根据 事件名来 获取想要了解的事件。
注意,每调用 一次 firePropertyChange方法就会调用  只调用一次  所有监听者 的 propertyChange 方法
 
(3)java.beans.PropertyChangeEvent (class)
位于观察者体内,观察者根据它做出相应处理动作;
主要的方法如下:
获取发生的事件名(事件名是通过 Support类的 fire PropertyChange函数的第一个参数自定义的 )。
public String getPropertyName()

 

获取发生改变的变量的新值。
public Object getNewValue()
获取发生改变的变量名。
public Object getOldValue()

 

获取发生改变的Bean对象
public Object getSource()

 

(4)实例:
题目:设有一股票,其价格在一定范围内波动,股民需要三种报告:股票的当前价格;股票价格的统计分析报告(最低价、最高价、均价);股票价格趋势预测。请运用观察者模式,编写上述股票行情程序(模拟股票价格的变动)。
提示:股价变动用随机数模拟:java.util.Random
//被监听的对象:股票变动类StockMarketChange.java
​package Observer;

import com.google.protobuf.Internal;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

//操控股市变化的类
public class StockMarketChange {
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    //记录历史的所有股市价格
    private List price;
    //趋势预测
    private boolean tendency;
    //统计分析报告
    private StatisticalData  statisticalData;

    /**
     * 股市初始化
     */
    public StockMarketChange() {
        this.price=new ArrayList<Double>();
        Random random = new Random();
        this.price.add((double)random.nextFloat()*100);
        //更新趋势预测
        int number = random.nextInt(2);
        if (number==1) {
            this.tendency = true;
        }
        else {
            this.tendency = false;
        }

        this.statisticalData=new StatisticalData(getNewPrice(),getNewPrice(),getNewPrice());
    }
    /**
     * 让股市价格变化并更新价格
     */
    private void changeNewPrice(){
        Random random = new Random();
        //改变后的值
        double newPrice = (double) random.nextFloat()*100;

        //改变前的值
        double oldPrice = (double)this.price.get(this.price.size()-1);

        //添加改变的值
        this.price.add(newPrice);
        firePropertyChange("股市价格发生变动",oldPrice,newPrice);
    }

    /**
     * 更新趋势预测
     */
    private void updateTendency(){
        if(this.price.size()>1) {
            double newPrice = (double)this.price.get(this.price.size()-1);
            double oldPrice = (double)this.price.get(this.price.size()-2);
            String oldTendency=getTendency();
            if(newPrice>=oldPrice){
                this.tendency = true;
            }else {
                this.tendency = false;
            }
            String newTendency=getTendency();
            if(!newTendency.equals(oldTendency)){
                firePropertyChange("股市趋势预测发生变动",oldTendency,newTendency);
            }
        }
    }
    /**
     * 更新统计分析报告
     */
    private void updateStatisticalData(){
        if(this.price.size()>1){
           StatisticalData oldStatisticalData=new StatisticalData(this.statisticalData);
           StatisticalData newStatisticalData=this.statisticalData;
           double max= (double) Collections.max(this.price);
           double min= (double) Collections.min(this.price);
           double average=0;
           for(int i=0;i<this.price.size();i++){
                average=average+(double)this.price.get(i);
           }
           average=average/this.price.size();
           newStatisticalData.setAverage(average);
           newStatisticalData.setMax(max);
           newStatisticalData.setMin(min);
           firePropertyChange("统计分析报告发生变动",oldStatisticalData,newStatisticalData);
        }
    }

    /**
     * 控制股市变化的函数
     */
    public void change(){
        changeNewPrice();
        updateTendency();
        updateStatisticalData();
    }

    /**
     * 获得趋势预测情况
     * @return
     */
    public String getTendency() {
        if(this.tendency){
            return "股市价格将上涨";
        }else {
            return "股市价格将下跌";
        }
    }

    /**
     * 获得统计分析报告
     * @return
     */
    public StatisticalData getStatisticalData() {
        return statisticalData;
    }

    /**
     * 获取股市最新价格
     * @return
     */
    public double getNewPrice(){
        double newPrice= (Double) price.get(price.size()-1);
        return newPrice;
    }

    /**
     * 添加监听者
     * @param listener 监听者
     */
    public void addPropertyChangeListener(PropertyChangeListener listener){
        this.pcs.addPropertyChangeListener(listener);
    }

    /**
     * 通知监听者
     * @param propName 给改变一个名字,好让监听者们根据这个名字来做响应
     * @param oldValue 改变前的值
     * @param newValue 改变后的值
     */
    public void firePropertyChange(String propName, Object oldValue, Object newValue) {
        this.pcs.firePropertyChange(propName, oldValue, newValue);
    }

    /**
     * 删除监听者
     * @param listener 监听者
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    /**
     * 打印用的方法
     * @return
     */
    @Override
    public String toString() {
        return "Stock{" +
                "price=" + price +
                ", tendency=" + tendency +
                ", statisticalData=" + statisticalData +
                '}';
    }
}

 

//存储统计分析结果的类 StatisticalData.java
package Observer;

public class StatisticalData {
    private double max;
    private double min;
    private double average;

    public StatisticalData(StatisticalData statisticalData) {
        this.average=statisticalData.getAverage();
        this.max=statisticalData.getMax();
        this.min=statisticalData.getMin();
    }

    public StatisticalData(double max, double min, double average) {
        this.max = max;
        this.min = min;
        this.average = average;
    }

    @Override
    public String toString() {
        return "StatisticalData{" +
                "max=" + max +
                ", min=" + min +
                ", average=" + average +
                '}';
    }

    public double getMax() {
        return max;
    }

    public void setMax(double max) {
        this.max = max;
    }

    public double getMin() {
        return min;
    }

    public void setMin(double min) {
        this.min = min;
    }

    public double getAverage() {
        return average;
    }

    public void setAverage(double average) {
        this.average = average;
    }
}

 

//监听者:用户类Client.java
package Observer;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class Client implements PropertyChangeListener {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        System.out.println("变化的对象为:"+evt.getSource());
        System.out.println();//空一行
        System.out.println("变化的情况为:"+evt.getPropertyName());
        System.out.println("变化前的内容:"+evt.getOldValue());
        System.out.println("变化后的内容:"+evt.getNewValue());
        System.out.println();//空一行
    }
}

 

//测试类 Demo.java
package demo;

import Observer.Client;
import Observer.StockMarketChange;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Demo {
    public static void main(String ages[]){
        StockMarketChange stockMarketChange=new StockMarketChange();
        Client client=new Client();
        stockMarketChange.change();
        stockMarketChange.addPropertyChangeListener(client);
        stockMarketChange.change();
        stockMarketChange.change();
        stockMarketChange.change();
        stockMarketChange.change();
    }
}

(5)总结:

笔者本人执行时,发现在上述代码中使用PropertyChangeSupport和PropertyChangeListener有一个问题:就是监听类无法监听到被监听类的构造函数中发生的事件。

 
 
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值