Java再会观察者模式

GOF定义

观察者模式(别名 : 依赖, 发布-订阅)
定义对象间的一种一对多的依赖关系, 当一个对象的状态发生变化时, 所有依赖它的对象都得到通知并被自动更新.

概述

观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式. 观察者模式中有一个称作"主题"的对象和若干个称作"观察者"的对象, “主题"和"观察者"间是一种一对多的依赖关系, 当"主题"的状态发生变化时, 所有"观察者"都得到通知. 前面所述的"求职中心"相当于观察者模式的一个具体"主题”; 每个"求职者"相当于观察者模式中的一个具体"观察者".

观察者模式的优点和适合使用观察者模式的情景

优点

  1. 具体主题和具体观察者是松耦合关系. 由于主题(Subject)接口仅仅依赖于观察者(Observer)接口, 因此具体主题只是知道它的观察者是实现观察者(Observer)接口的某个类的实例, 但不需要知道具体哪个类. 同样, 由于观察者仅仅依赖于主题(Subject)接口, 因此具体观察者只是知道它依赖的主题是实现主题(Subject)接口的某个类的实例, 但不需要知道具体是哪个类.
  2. 观察者模式满足"开-闭原则". 主题(Subject)接口仅仅依赖于观察者(Observer)接口, 这样, 就可以让创建具体主题的类也仅仅是依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类, 不必修改创建具体主题的类的代码. 同样, 创建具体观察者的类仅仅依赖于主题(Observer)接口, 如果增加新的实现主题(Subject) 接口的类, 也不必修改创建具体观察者类的代码.

适合使用观察者模式的情景

  1. 当一个对象的数据更新时需要通知其他对象, 但这个对象又不希望和被通知的那些对象形成紧耦合.
  2. 当一个对象的数据更新时, 这个对象需要让其他对象也各自更新自己的数据, 但这个对象不知道具体有多少个对象需要更新数据.

模式的结果与使用

四种角色

主题(Subject)

主题是一个接口, 该接口规定了具体主题需要实现的方法, 比如, 添加,删除观察者以及通知观察者更新数据的方法

观察者(Observer)

观察者是一个接口, 该接口规定了具体观察者用来更新数据的方法

具体主题(ConcreteSubject)

具体主题是实现主题接口类的一个实例, 该实例包含有可能经常发生变化的数据. 具体主题需要使用一个集合, 比如 ArrayList, 存放观察者的引用, 以便数据变化时通知具体观察者

具体观察者(ConcreteObserver)

具体观察者是实现观察者接口类的一个实例, 具体观察者包含有可以存放具体主题引用的主题接口变量, 以便具体观察者让具体主题将自己的引用添加到具体的集合中, 使自己成为它的观察者, 或让这个具体主题将自己从具体主题的集合中删除, 使自己不再是它的观察者.

案例一 :简单观察者模式(推数据模式)

在这里插入图片描述

package com.beyond.cwq.observer;

import java.io.File;
import java.io.RandomAccessFile;
import java.util.ArrayList;

/**
 * 观察者
 */
interface Observer {
	public void hearTelephone(String heardMess);
}

/**
 * 主题
 */
interface Subject {
	public void addObserver(Observer observer);

	public void deleteObserver(Observer observer);

	public void notifyObserver();
}

/**
 * 具体主题
 */
class SeekJobCenter implements Subject {
	String mess;
	boolean changed;
	ArrayList<Observer> personList;

	public SeekJobCenter() {
		personList = new ArrayList<Observer>();
		mess = "";
		changed = false;
	}                               

	@Override
	public void addObserver(Observer observer) {
		if (!personList.contains(observer)) {
			personList.add(observer);
		}
	}

	@Override
	public void deleteObserver(Observer observer) {
		if (personList.contains(observer)) {
			personList.remove(observer);
		} else {
			System.out.println("该观察者已经阵亡!");
		}
	}

	@Override
	public void notifyObserver() {
		if (changed) {
			for (int x = 0; x < personList.size(); x++) {
				Observer observer = personList.get(x);
				observer.hearTelephone(mess);
			}
			changed = false;
		}
	}

	public void giveNewMess(String str) {
		if (str.equals(mess)) {
			changed = false;
		} else {
			mess = str;
			changed = true;
		}

	}
}

/**
 * 具体观察者 01
 */
class UniversityStudent implements Observer {
	Subject subject;
	File myFile;

	public UniversityStudent(Subject subject, String fileName) {
		this.subject = subject;
		subject.addObserver(this);
		myFile = new File(fileName);
	}

	@Override
	public void hearTelephone(String heardMess) {
		try {
			RandomAccessFile out = new RandomAccessFile(myFile, "rw");
			out.seek(out.length()); // 设置文件写入的位置, 即偏移量,这是在文件的末尾写入
			byte data[] = heardMess.getBytes();
			out.write(data); // 更新文件的内容
			System.out.println("我是一个大学生!");
			System.out.println("我向文件" + myFile.getName() + " 写入如下内容: ");
			System.out.println(heardMess);
		} catch (Exception e) {
		}

	}

}

/**
 * 具体观察者 02
 */
class HaiGui implements Observer {

	Subject subject;
	File myFile;

	public HaiGui(Subject subject, String fileName) {
		this.subject = subject;
		subject.addObserver(this);
		myFile = new File(fileName);
	}
                                        
	@Override
	public void hearTelephone(String heardMess) {
		try {
			boolean boo = heardMess.contains("java 程序员") || heardMess.contains("软件");
			if (boo) {
				RandomAccessFile out = new RandomAccessFile(myFile, "rw");
				out.seek(out.length()); // 设置文件写入的位置, 即偏移量,这是在文件的末尾写入
				byte data[] = heardMess.getBytes();
				out.write(data); // 更新文件的内容
				System.out.println("我是一个海归!");
				System.out.println("我向文件" + myFile.getName() + " 写入如下内容: ");
				System.out.println(heardMess);
			} else {
				System.out.println("我是海归,这次的信息没有我需要的!");
			}

		} catch (Exception e) {
		}

	}
}

public class ObserverDeom01 {
	public static void main(String[] args) {
		SeekJobCenter center = new SeekJobCenter();  // 具体主题
		UniversityStudent us = new UniversityStudent(center, "D:"+File.separator+"a.txt");
		HaiGui hg = new HaiGui(center, "D:"+File.separator+"a.txt");
		center.giveNewMess("我要10个java程序员!");
		center.notifyObserver();
		center.giveNewMess("我需要10个C语言架构师!");
		center.notifyObserver();
	}
}

在这里插入图片描述

观察者模式中的"推"数据和"拉"数据

1. 推数据方式

推数据方式是指具体主题将变化后的数据全部交给具体观察者, 即将变化后的数据传递给具体观察者用于更新数据方法的参数. 当具体主题认为具体观察者需要这些变换后的全部数据时往往采用推数据方式.

2.拉数据方式

拉数据方式是指具体主题不将变化后的数据交给具体观察者, 而是提供了获得这些数据的方法, 具体观察者在得到通知后, 可以调用具体主题提供的方法得到数据(观察者自己把数据"拉"过来), 但需要自己判断数据是否发生了变化. 当具体主题不知道具体观察者是否需要这些变换后的数据时往往采用拉数据的方式.

案例: 拉数据模式

在这里插入图片描述

package com.beyond.cwq.observer;

import java.util.ArrayList;

/**
 * 主题
 */
interface Subject02 {
	public void addObserver(Observer02 observer);

	public void deleteObserver(Observer02 observer);

	public void notifyObservers();
}

/**
 * 观察者
 */
interface Observer02 {
	public void update();
}

class ShopSubject implements Subject02 {

	private String goodName;
	private double oldPrice, newPrice;
	ArrayList<Observer02> customList;

	public ShopSubject() {
		customList = new ArrayList<Observer02>();

	}

	@Override
	public void addObserver(Observer02 observer) {
		if (!customList.contains(observer)) {
			customList.add(observer);
		}
	}

	@Override
	public void deleteObserver(Observer02 observer) {
		if (customList.contains(observer)) {
			customList.remove(observer);
		}
	}

	@Override
	public void notifyObservers() {
		for (int i = 0; i < customList.size(); i++) {
			Observer02 observer = customList.get(i);
			observer.update(); // 仅仅让观察者执行更新操作, 但不提供数据
		}
	}

	public void setDiscountGoods(String name, double oldP, double newP) {
		goodName = name;
		oldPrice = oldP;
		newPrice = newP;
		notifyObservers();
	}

	public String getGoodsName() {
		return goodName;
	}

	public double getOldPrice() {
		return oldPrice;
	}

	public double getNewPrice() {
		return newPrice;
	}

}

/**
 * 具体观察者
 */
class CustomerOne implements Observer02 {
	private Subject02 subject;
	private String goodsName, personName;

	public CustomerOne(Subject02 subject, String personName) {
		this.subject = subject ;
		this.personName = personName;
		subject.addObserver(this);
	}

	
	@Override
	public void update() {
		if (subject instanceof ShopSubject) {  // instanceof 测试一个对象是否是某一个类的实例
			goodsName = ((ShopSubject)subject).getGoodsName();
			System.out.println(personName + "只是对打折的商品感兴趣!");
			System.out.println("打折的商品是: "+ goodsName);
		}
	}
}

/**
 * 具体观察者 2
 */
class CustomerTwo implements Observer02{
	private Subject02 subject;
	private double oldPrice, newPrice;
	private String personName;
	public CustomerTwo(Subject02 subject,String personName) {
		this.subject = subject;
		this.personName = personName;
		subject.addObserver(this);
	}
	
	
	@Override
	public void update() {
		if (subject instanceof ShopSubject) {
			oldPrice = ((ShopSubject)subject).getOldPrice();
			newPrice = ((ShopSubject)subject).getNewPrice();
			System.out.println(personName + "只对商品的价格感兴趣!");
			System.out.println("原价是: "+oldPrice);
			System.out.println("现价是: "+newPrice);
		}
	}
}


public class ObserverDemo02 {
	public static void main(String[] args) {
		ShopSubject shop = new ShopSubject();
		CustomerOne boy = new CustomerOne(shop, "张三");
		CustomerTwo girl = new CustomerTwo(shop, "小天");
		shop.setDiscountGoods("我变了变了", 12.5, 45);
		shop.setDiscountGoods("我回来了回来了", 454, 47);
		//shop.notifyObservers();
	}
}

在这里插入图片描述

观察者与多主题

一个观察者可以依赖于多个具体主题, 当所依赖的任何具体主题的数据发生变化时, 该观察者都能得到通知. 多主题所涉及的主要问题是观察者如何处理主题中变化后的数据, 因为, 不同的具体主题所含有的数据的结构有可能大有不同.
在处理多主题时, 主题应当采用拉数据模式, 观察者接口可以将更新数据方法的参数类型设置为主题接口类型, 比如public void update(Subject subject), 即具体主题数据发生变化时将自己的引用传递给具体观察者, 然后具体观察者让这个具体主题调用有关的方法返回该具体主题中的数据.

案例: 观察者与多主题: 拉数据模式

在这里插入图片描述

package com.beyond.cwq.observer;

import java.util.ArrayList;

interface Subject03 {
	public void addObserver(Observer03 observer);

	public void deleteObserver(Observer03 observer);

	public void notifyObservers();
}

interface Observer03 {
	public void update(Subject03 subject);
}

class WeatherStation implements Subject03 {
	private String forecastTime, forecastMess;
	private int maxTemperature, minTemperature;
	ArrayList<Observer03> personList;

	public WeatherStation() {
		personList = new ArrayList<Observer03>();
	}

	@Override
	public void deleteObserver(Observer03 observer) {
		if (personList.contains(observer)) {
			personList.remove(observer);
		}
	}

	@Override
	public void notifyObservers() {
		for (int x = 0; x < personList.size(); x++) {
			Observer03 observer = personList.get(x);
			observer.update(this);
		}
	}

	@Override
	public void addObserver(Observer03 observer) {
		if (observer == null) {
			return;
		}
		if (!(personList.contains(observer))) {
			personList.add(observer);
		}
	}

	public void doForecast(String t, String mess, int max, int min) {
		forecastMess = mess;
		forecastTime = t;
		maxTemperature = max;
		minTemperature = min;
		notifyObservers();
	}

	public String getForecastTime() {
		return forecastTime;
	}

	public String getForecastMess() {
		return forecastMess;
	}

	public int getMaxTemperature() {
		return maxTemperature;
	}

	public int getMinTemperature() {
		return minTemperature;
	}

}

class TravelAgency implements Subject03 {
	private String tourStartTime;
	private String tourMess;
	private ArrayList<Observer03> personList;
	
	public TravelAgency() {
		personList = new ArrayList<Observer03>();
	}
	
	@Override
	public void addObserver(Observer03 observer) {
		if (!personList.contains(observer)) {
			personList.add(observer);
		}
	}

	@Override
	public void deleteObserver(Observer03 observer) {
		if (personList.contains(observer)) {
			personList.remove(observer);
		}
	}

	@Override
	public void notifyObservers() {
		for (int x = 0; x < personList.size(); x++) {
			Observer03 observer = personList.get(x);;
			observer.update(this);
		}
	}
	
	public void giveMess(String time, String mess) {
		tourStartTime = time;
		tourMess = mess;
		notifyObservers();
	}
	
	public String getTourStartTime() {
		return tourStartTime;
	}
	public String getTourMess() {
		return tourMess;
	}
	
}

/**
 * 具体观察者
 */
class Person implements Observer03{
	
	private Subject03 subjectOne,subjectTwo;
	private String forecastTime,forecastMess;
	private String tourStartTime,tourMess;
	private int maxTemperature,minTemperature;
	public Person(Subject03 subjectOne,Subject03 subjectTwo) {
		this.subjectOne = subjectOne;
		this.subjectTwo = subjectTwo;
		subjectOne.addObserver(this);
		subjectTwo.addObserver(this);
	}
	
	@Override
	public void update(Subject03 subject) {
		if (subject instanceof WeatherStation) {
			WeatherStation WS = (WeatherStation)subject;
			forecastTime = WS.getForecastTime();
			forecastMess = WS.getForecastMess();
			maxTemperature = WS.getMaxTemperature();
			minTemperature = WS.getMinTemperature();
			System.out.print("预报日期:"+ forecastTime +",");
			System.out.print("天气状况:"+ forecastMess +",");
			System.out.print("最好温度:"+ maxTemperature +",");
			System.out.print("最低温度:"+ minTemperature +".");
		}else if (subject instanceof TravelAgency) {
			TravelAgency TA = (TravelAgency)subject;
			tourStartTime = TA.getTourStartTime();
			tourMess = TA.getTourMess();
			System.out.print("旅游开始日期: " + tourStartTime + ",");
			System.out.println("旅游信息:"+ tourMess+".");
			
		}
	}
}


public class ObserverDemo03 {
	public static void main(String[] args) {
		WeatherStation weatherStation = new WeatherStation();
		TravelAgency travelAgency = new TravelAgency();
		Person xiaoLi = new Person(weatherStation, travelAgency);
		weatherStation.doForecast("10日", "阴转小雨", 28, 20);
		travelAgency.giveMess("10日", "黄山两日游");
		weatherStation.doForecast("11日", "阴转小雨", 32, 0);
		travelAgency.giveMess("11日", "丽江一日游");
		
	}
}

在这里插入图片描述

Java API 中的 Observable 类 与Observer 接口

在这里插入图片描述

Observable 类与 Observer接口的作用与优缺点

1. 作用

由于观察者模式是 Java 程序设计中使用较广泛的模式之一, java.util 包提供了用来设计符合观察者模式的 Observable 类与 Observer 接口. 其中 Observable 类相当于观察者模式中的主题接口, Observable 类的子类的实例称作一个具体的"可观察者", 这里仍然将Observable类的子类的实例称作一个具体主题. Observer接口与观察者模式中的观察者接口相同, 实现该接口的类的实例称作一个具体观察者.

2. 优点

如果软件设计者在需要使用观察者模式来设计某个系统时, 各自定义自己的主题和观察者, 显然不利于系统之间的复用. 例如: 某个设计者设计的系统中的一个气象站主题, 该气象站主要维护英国的天气信息; 另一个设计者的设计的系统中也有一个气象站主题, 该气象站主要维护中国的天气信息; 那么维护英国天气信息气象站的观察者可能就无法成为维护中国天气信息气象站的观察者, 因为两个设计者使用了不同的接口名称. 如果在使用观察者模式设计系统时, 能够统一使用java.util 包中的 Observable 类与 Observer 接口将有利于系统之间的复用.

3. 缺点

值得注意的是: Java API 给出的支持用于观察者设计的 Observable 不是一个接口, 而是一个类. 尽管该类为它的子类提供了很多可以直接使用的方法, 但同时也带来一个问题: Observable 的子类无法使用继承方式复用其他类的方法, 其原因是 java 不支持多继承, 即一个类只能有一个父类. 另外, Observable 类没有使用 JDK 1.5 版本后的集合, 在编译时会得到 : Note : uses unchecked or unsafe operations 警告信息,但不影响运行

Observable 类中的主要方法

Observable 类是 Java API 提供的类, 可以查看 JDK 安装目录下的 src.zip 文件获得该类的源代码

1. addObserver (Observer o) 方法

Observable 类有一个访问权限是 private 的 Vector 型成员变量 obs, 用来存放具体观察者的引用, addObserver(Observer o) 的代码如下:

 public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

Observable 类的子类只可以继承该方法, 因为父类的成员变量 obs 的访问权限是private, 子类只可以使用父类的 addObserver(Observer o) 方法来操作 obs, 不能通过重写addObserver(Observer o ) (覆盖) 方法来访问成员变量 obs.

2. setChanged() 方法

Observable 类有一个访问权限是 private 的 boolean 型成员变量 changed, setChanged() 方法的代码是:

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

当具体主题的数据发生变化时就可以调用该方法, 表明具体主题维护的数据已经发生变化. 由于父类的成员变量 changed 的访问权限是 private, 子类只能通过使用父类的 setChanged() 方法来操作 changed, 因此, Observable 类的子类只能选择继承该方法, 不能是 protected, 只有子类可以直接使用该方法, 这意味着其他类即使含有 Observable 类或其他子类的实例, 该实例也无法调用 setChanged() 方法( 除非这个"其他"类的包名是 java.util, 但是 Java 运行系统不允许用户程序中的类拥有这样的包名, 尽管可以编译拥有 java.util 包名的类, 但是运行时 JVM 拒绝加载用户的类) .

clearChanged() 方法

该方法将 changed 的值设置为 false, 代码如下:

protected synchronized void clearChanged(){
	changed = false;
}

当具体主题的数据发生变化, 并且通知了所有观察者之后, 需要调用该方法 将 changed 的值设置为 false.

hasChanged() 方法

该方法返回 changed 的值, 代码如下:

public synchronized boolean hasChanged(){
	return changed;
}

具体主题在通知具体观察者之前, 应当调用该方法判断数据是否已经发生变化.

notifyObservers(Object arg) 方法

如果 hasChanged() 方法返回的值是true, 则通知其所有观察者, 并调用 clearChanged() 方法将changed 值设置为 false, 代码如下:

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)allLocal[i]).update(this.arg);
	}
}

由于父类的成员变量 obs 的访问权限是 private, 子类只能通过使用父类的方法来操作 obs, 因此, Observable 类的子类只能选择继承该方法, 不能通过重写覆盖的方式来操作obs.

notifyObservers() 方法

该方法的实现是 notifyObservers(Object arg) 方法参数 arg 为 null 的情况, 代码如下 :

public void notifyObservers(){
	notifyObservers(null);
}

Observer 接口中的主要方法

public void update(Observable o, Object arg) 方法
具体主题执行 notifyObservers () 方法时, 会让具体观察者调用 update() 方法, 以便向观察者通知改变. 具体主题将自己的引用传递给 update() 方法的参数, 以便具体观察者获取具体主题中的数据.

案例: 使用 Observable 类与 Observer 接口

package com.beyond.cwq.observer;

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



/**
 * 具体主题 继承 Observable 
 * @author DHL
 */
class WaterMessStation extends Observable{
	private double waterVelocity;  // 水流速度
	private double waterDischange;  // 水流量
	
	public void giveMess(double waterVelocity,double waterDischange) {
		if (this.waterVelocity != waterVelocity || this.waterDischange != waterDischange) {
			setChanged();
			this.waterVelocity = waterVelocity;
			this.waterDischange = waterDischange;
			notifyObservers();
			
		}
	}
	
	public double getWaterVelocity() {
		return waterVelocity;
	}
	
	public double getWaterDischarge() {
		return waterDischange;
	}
}

/**
 * 具体观察者 实现 Observer 接口
 * @author DHL
 */
class WaterDepartment implements Observer{
	Observable subject;
	private double waterVelocity;  // 水流速度
	private double waterDischarge;  // 水流量
	
	public WaterDepartment(Observable subject) {
		this.subject = subject;
		subject.addObserver(this);
	}
	@Override
	public void update(Observable o, Object arg) {
		if (subject instanceof WaterMessStation) {
			WaterMessStation WMS = (WaterMessStation)subject;
			waterVelocity = WMS.getWaterVelocity();
			waterDischarge = WMS.getWaterDischarge();
			System.out.print("水流速度是(米/秒): " + waterVelocity + ",");
			System.out.println("流量(立方米/秒): " + waterDischarge);
		}
	}
}

public class ObserverDemo04 {
	public static void main(String[] args) {
		WaterMessStation waterStation = new WaterMessStation();  // 具体主题
		WaterDepartment ZhiHuiBu = new WaterDepartment(waterStation); // 具体观察者
		waterStation.giveMess(10, 209.9);
		waterStation.giveMess(11, 219.8);
		waterStation.giveMess(7, 127);
		
		
	}
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_大木_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值