文档章节

OpenJDK源码研究笔记(六)--观察者模式工具类(Observer和Observable)和应用示例

FansUnion
 FansUnion
发布于 2015/10/22 10:29
字数 1716
阅读 2
收藏 0
点赞 0
评论 0

本文主要讲解OpenJDK观察者模式的2个工具类,java.util.Observer观察者接口,java.util.Observable被观察者基类。

然后,给出了一个常见的观察者应用示例。

Observer观察者接口

/**
 * 一个类可以实现Observer接口,当它想要得到“被观察者”对象变化通知的时候

  */
public interface Observer {
    /**
     * 当被观察的对象发生变化时,这个方法被调用。一个应用调用Observable对象的notifyObservers方法,去通知所有的观察者,   Observable对象发生了变化。

     * @param   o     被观察的对象.
     * @param   arg   传递给notifyObservers方法的参数.
     */
    void update(Observable o, Object arg);
}


Observable被观察者基类

这个类代表一个可以观察的对象,或数据(在模型-视图范式中)。
它能够被继承去代表一个应用想要观察的对象。

一个observable对象可以有一个或多个观察者。
一个观察者可以是一个实现了Observer接口的对象。

当一个observable实例发生了变化,一个应用可以调用Observable的notifyObservers方法,去通知
它的观察者所发生的变化,通过调用观察者的update方法。

通知被传递的顺序是没有指定的。
在Observable类中的默认实现是,按注册顺序通知Observers。
但是子类可以改变这种顺序,使用没有保证的顺序,在分开的线程中传递通知,或者确保他们的子类遵循他们所选择的这个顺序。

注意:这个通知机制和线程没有任何关系,与Object对象的wait和notify机制没有任何关系。

当一个observable被新创建的时候,它的observer集合是空的。
两个observer被认为是同一个,当且仅当他们的equals方法比较返回true的时候。

public class Observable {

    //该对象是否已经发生了变化,默认为false
    private boolean changed = false;

   //Vector是线程安全的,ArrayList是非线程安全的
    private Vector obs;

   //构造一个没有观察者的Observable对象

     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) {
             //我们不想要Observer 回调任何代码,在保持它的Monitor的时候。

            //从Vector中提取Observable 和存储Observer的状态的代码需要同步,但是通知观察者却不需要。

            //最坏的潜在竞争性条件结果是:

            //1.一个新增加的观察者将会错误正在进行的通知

            //2.一个最近被取消注册的观察者将会被错误的通知,当它不需要关心的时候
            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();
    }

//表明Observable对象已经发生了变化

protected synchronized void setChanged() {
        changed = true;
    }

     //表明这个对象已经不再变化,或者说明已经通知到所有的观察者最近的一次变化。
    protected synchronized void clearChanged() {
        changed = false;
    }

    //测试该对象是否已经发生了变化。当且仅当setChanged方法比clearChanged方法调用的次数更多的时候,返回true。

    public synchronized boolean hasChanged() {
        return changed;
    }

    
   // 返回Observable对象的观察者的数量.
    public synchronized int countObservers() {
         return obs.size();
    }

}


代码示例

书,被观察者(或者称之为订阅主题)

import java.util.Observable;

//书,被观察者(或者称之为订阅主题)
public class Book extends Observable {

    // 书的ID
    private Integer id;
    // 书名
    private String name;
    // 当前价格
    private Double price;

    // 当书的价格修改时,调用该方法
    public void modifyPrice(Double newPrice) {
        ChangeStatus status = null;
        // 差值大于0.01,就认为价格放生了变化
        if (Math.abs(newPrice - price) >= 0.01) {
            setChanged();
            status = new ChangeStatus();
            status.setId(id);
            status.setName(name);
            status.setOldPrice(price);
            status.setNewPrice(newPrice);

        }
        // 通知客户书已经降价
        notifyObservers(status);

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

}


观察者:用户的邮箱

import java.util.Observable;
import java.util.Observer;

/**
 * 观察者:用户的邮箱
 */
public class BuyerEmail implements Observer {

    // 该方法会被“被观察者的父类”即Observable调用
    public void update(Observable o, Object arg) {
        // 这里做具体的发电子邮件的程序
        if (arg instanceof ChangeStatus) {
            NotifyUtils.sendEmail((ChangeStatus) arg);
        }
    }
}


观察者:用户的手机

import java.util.Observable;
import java.util.Observer;
/**
 * 观察者:用户的手机
 */
public class BuyerMobile implements Observer {

    // 该方法会被“被观察者的父类”即Observable调用
    public void update(Observable o, Object arg) {
        // 这里做具体的发电子邮件的程序
        if(arg instanceof ChangeStatus){
            NotifyUtils.sendSMS((ChangeStatus)arg);
        }
    }

}


一本书的信息变化的实体类

public class ChangeStatus {
    //书的ID
    private Integer id;
    //书名
    private String name;
    //过去的价格
    private Double oldPrice;
    //最新的价格
    private Double newPrice;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Double getOldPrice() {
        return oldPrice;
    }

    public void setOldPrice(Double oldPrice) {
        this.oldPrice = oldPrice;
    }

    public Double getNewPrice() {
        return newPrice;
    }

    public void setNewPrice(Double newPrice) {
        this.newPrice = newPrice;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}


发送通知的工具类

public class NotifyUtils {
    // 发送邮件通知用户
    public static void sendEmail(ChangeStatus status) {
        print(status);
    }

    // 发送短信通知用户
    public static void sendSMS(ChangeStatus status) {
        print(status);
    }

    // 一种只是用来作为“演示”的发送信息的方法
    private static void print(ChangeStatus status) {
        println("*******************************************************");
        String messageContent = messageContent(status);
        println(messageContent);
        println("********************************************************");
    }

    private static String messageContent(ChangeStatus status) {
        // 一般,降价才会通知,这里只当是降价了
        String str = "尊敬的用户,您好!\n";
        str += "您收藏的图书《" + status.getName() + "》降价了。\n";
        str += "过去的价格是:" + status.getOldPrice() + "\n";
        str += "现在的价格是:" + status.getNewPrice() + "\n";
        str += "您可以通过以下链接 http://blog.csdn.net/FansUnion 进行购买。";
        return str;
    }

    private static void println(Object content) {
        System.out.println(content);
    }
}


程序入口

public class ObserverPatternTest {

    /**
     * 观察者模式-用法示例
     */
    public static void main(String[] args) {
        Book book = new Book();
        book.setName("中国象棋程序的设计与实现");
        book.setPrice(45.0);

        // 下面的观察者在实际应用中可以从数据库或文件中读取
        BuyerEmail email = new BuyerEmail();
        BuyerMobile mobile = new BuyerMobile();

        // 增加观察者,在实际应用中就是哪些人对该书作了关注
        book.addObserver(email);
        book.addObserver(mobile);

        book.modifyPrice(34.00);

    }
}


输出结果

*******************************************************
尊敬的用户,您好!
您收藏的图书《中国象棋程序的设计与实现》降价了。
过去的价格是:45.0
现在的价格是:34.0
您可以通过以下链接 http://blog.csdn.net/FansUnion 进行购买。
********************************************************
*******************************************************
尊敬的用户,您好!
您收藏的图书《中国象棋程序的设计与实现》降价了。
过去的价格是:45.0
现在的价格是:34.0
您可以通过以下链接 http://blog.csdn.net/FansUnion 进行购买。
********************************************************

参考资料:OpenJDK7源码

原文参见: http://FansUnion.cn/articles/2936

版权声明:本文为博主原创文章,未经博主允许不得转载。

© 著作权归作者所有

共有 人打赏支持
FansUnion
粉丝 56
博文 857
码字总数 825464
作品 0
丰台
高级程序员

暂无相关文章

vuex学习

1、getters基本用法: 在store.js里面用const声明我们的getters属性。 const getters={ count:function (state) { return state.count +=100; }} export default new Vuex.S......

大美琴 ⋅ 40分钟前 ⋅ 0

292. Nim Game - LeetCode

Question 292. Nim Game Solution 思路:试着列举一下,就能发现一个n只要不是4的倍数,就能赢。 n 是否能赢1 true2 true3 true4 false 不论删除几,对方都能一把赢5 t...

yysue ⋅ 52分钟前 ⋅ 0

G6 关系数据可视化图形库 简单使用

官网 https://antv.alipay.com/zh-cn/g6/1.x/index.html 效果 首先生成给定数目的小球,并设置随机的颜色 按照顺序,设置小球的角度以及坐标 设置定时器,每隔一定的时间修改小球的角度和坐标...

阿豪boy ⋅ 54分钟前 ⋅ 0

6.5 zip压缩工具 6.6 tar打包 6.7 打包并压缩

zip压缩工具 zip命令可以压缩目录和文件,-r 压缩目录。 zip使用方法 zip 1.txt.zip 1.txt //压缩文件 zip -r 123.zip 123/ //压缩目录 unzip 1.txt.zip //解压 unzip 123.zip -d /root/456...

Linux_老吴 ⋅ 今天 ⋅ 0

react-loadable使用跳坑

官方给react-loadable的定义是: A higher order component for loading components with dynamic imports. 动态路由示例 withLoadable.js import React from 'react'import Loadable fro......

pengqinmm ⋅ 今天 ⋅ 0

记录工作中遇到的坑

1、ios safari浏览器向下滚动会触发window resize事件

端木遗风 ⋅ 今天 ⋅ 0

桥接设计模式

1、概述: 将抽象部分与他的实现部分分离,这样抽象化与实现化解耦,使他们可以独立的变化 如何实现解耦的呢,就是通过提供抽象化和实现化之间的桥接结构 桥接模式将继承模式转化成关联关系,他降...

职业搬砖20年 ⋅ 今天 ⋅ 0

20.zip压缩 tar打包 打包并压缩

6月25日任务 6.5 zip压缩工具 6.6 tar打包 6.7 打包并压缩 6.5 zip压缩工具: zip支持压缩目录 zip压缩完之后原来的文件不删除 不同的文件内容其实压缩的效果不一样 文件内有很多重复的用xz压...

王鑫linux ⋅ 今天 ⋅ 0

double类型数据保留四位小数的另一种思路

来源:透析公式处理,有时候数据有很长的小数位,有的时候由在四位以内,如果用一般的处理方法,那么不足四位的小树会补充0到第四位,这样子有点画蛇添足的感觉,不太好看。所以要根据小数的...

young_chen ⋅ 今天 ⋅ 0

Django配置163邮箱出现 authentication failed(535)错误解决方法

最近用Django写某网站,当配置163邮箱设置完成后,出现535错误即:smtplib.SMTPAuthenticationError: (535, b'Error: authentication failed') Django初始配置邮箱设置 EMAIL_HOST = "smtp.1...

陈墨轩_CJX ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部