设计模式(十八)——观察者模式(Observer Pattern)

观察者模式(Observer Pattern)

基本介绍

对象之间多对一依赖的一种设计方案,被依赖的对象为 Subject,依赖的对象为 Observer,Subject
通知 Observer 变化。

场景分析

  • 聊天室程序的创建。服务器创建好后,A,B,C三个客户端连上来公开聊天。A向服务器发送数据,服务器端聊天数据改变。我们希望将这些聊天数据分别发给其他在线的客户。也就是说,每个客户端需要更新服务器端得数据。
  • 网站上,很多人订阅了”java主题”的新闻。当有这个主题新闻时,就
    会将这些新闻发给所有订阅的人。
  • 大家一起玩CS游戏时,服务器需要将每个人的方位变化发给所有的客户。

上面这些场景,我们都可以使用观察者模式来处理。我们可以把多个订阅者、客户称之为观察者; 需要同步给多个订阅者的数据封装到对象中,称之为目标。

核心

观察者模式主要用于1 : N的通知。当一个对象(目标对象Subject或Objservable)的状态变化时(消息发布);他需要及时告知一系列对象(观察者对象,Observer) ,令他们做出响应(消息订阅)。

通知观察者的方式:

消息订阅
●推

  • 每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接收。

●拉

  • 观察者只要直到有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。

观察者模式的UML类图

在这里插入图片描述

角色分析

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer/Publisher)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

开发中常见的场景:

  • 聊天室程序的,服务器转发给所有客户端
  • 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发
  • 邮件订阅
  • Servlet中,监听器的实现
  • Android中,广播机制
  • JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(Delegation Event
    Model)
    • 事件源----------------目标对象
    • 事件监听器------------观察者
  • 京东商城中,群发某商品打折信息

实例分析

天气预报项目:

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  2. 需要设计开放型 API,便于其他第三方也能接入气象站获取数据。
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方。

传统方案实现

在这里插入图片描述
在这里插入图片描述

代码实现

/**
 * 类是核心
 * 1. 包含最新的天气情况信息 
 * 2. 含有 CurrentConditions 对象
 * 3. 当数据有更新时,就主动的调用   CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息
 * @author Administrator
 *
 */
public class WeatherData {
	private float temperatrue;
	private float pressure;
	private float humidity;
	private CurrentConditions currentConditions;
	//加入新的第三方

	public WeatherData(CurrentConditions currentConditions) {
		this.currentConditions = currentConditions;
	}

	public float getTemperature() {
		return temperatrue;
	}

	public float getPressure() {
		return pressure;
	}

	public float getHumidity() {
		return humidity;
	}

	public void dataChange() {
		//调用 接入方的 update
		currentConditions.update(getTemperature(), getPressure(), getHumidity());
	}

	//当数据有更新时,就调用 setData
	public void setData(float temperature, float pressure, float humidity) {
		this.temperatrue = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
		dataChange();
	}
}
/**
 * 显示当前天气情况(可以理解成是气象站自己的网站)
 */
public class CurrentConditions {
	// 温度,气压,湿度
	private float temperature;
	private float pressure;
	private float humidity;

	//更新 天气情况,是由 WeatherData 来调用,我使用推送模式
	public void update(float temperature, float pressure, float humidity) {
		this.temperature = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		display();
	}

	//显示
	public void display() {
		System.out.println("***Today mTemperature: " + temperature + "***");
		System.out.println("***Today mPressure: " + pressure + "***");
		System.out.println("***Today mHumidity: " + humidity + "***");
	}
}

客户端调用

public class Client {
	
	public static void main(String[] args) {
		//创建接入方 currentConditions
		CurrentConditions currentConditions = new CurrentConditions();
		//创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中
		WeatherData weatherData = new WeatherData(currentConditions);
		
		//更新天气情况
		weatherData.setData(30, 150, 40);
		
		//天气情况变化
		System.out.println("============天气情况变化=============");
		weatherData.setData(40, 160, 20);
		
	}
}

问题分析

  1. 其他第三方接入气象站获取数据的问题

  2. 无法在运行时动态的添加第三方 (新浪网站)

  3. 违反 ocp 原则=>观察者模式
    在 WeatherData 中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到 dataChange, 不利于维护,也不是动态加入

观察者模式实现

UML类图

在这里插入图片描述

代码实现

抽象主题
public interface Subject {
	//注册观察者
	void registerObserver(Observer o);

	//移除观察者
	void removeObserver(Observer o);

	//通知观察者
	void notifyObservers();
	
}
具体主题
/**
 * 类是核心
 * 1. 包含最新的天气情况信息 
 * 2. 含有 观察者集合,使用ArrayList管理
 * 3. 当数据有更新时,就主动的调用   ArrayList, 通知所有的(接入方)就看到最新的信息
 */
public class WeatherData implements Subject {
	private float temperatrue;
	private float pressure;
	private float humidity;
	//观察者集合
	private ArrayList<Observer> observers;
	
	//加入新的第三方

	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

	public float getTemperature() {
		return temperatrue;
	}

	public float getPressure() {
		return pressure;
	}

	public float getHumidity() {
		return humidity;
	}

	public void dataChange() {
		//调用 接入方的 update
		
		notifyObservers();
	}

	//当数据有更新时,就调用 setData
	public void setData(float temperature, float pressure, float humidity) {
		this.temperatrue = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		//调用dataChange, 将最新的信息 推送给 接入方 currentConditions
		dataChange();
	}

	//注册一个观察者
	@Override
	public void registerObserver(Observer o) {
		observers.add(o);
	}

	//移除一个观察者
	@Override
	public void removeObserver(Observer o) {
		if(observers.contains(o)) {
			observers.remove(o);
		}
	}

	//遍历所有的观察者,并通知
	@Override
	public void notifyObservers() {
		for(int i = 0; i < observers.size(); i++) {
			observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
		}
	}
}

抽象观察者
public interface Observer {

	void update(float temperature, float pressure, float humidity);
}
具体观察者
public class BaiduSite implements Observer {

	// 温度,气压,湿度
	private float temperature;
	private float pressure;
	private float humidity;

	// 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
	@Override
    public void update( float temperature, float pressure, float humidity) {
		this.temperature = temperature;
		this.pressure = pressure;
		this.humidity = humidity;
		display();
	}

	// 显示
	public void display() {
		System.out.println("===百度网站====");
		System.out.println("***百度网站 气温 : " + temperature + "***");
		System.out.println("***百度网站 气压: " + pressure + "***");
		System.out.println("***百度网站 湿度: " + humidity + "***");
	}

}

客户端调用
public class Client {

	public static void main(String[] args) {
		//创建接入方 currentConditions
		CurrentConditions currentConditions = new CurrentConditions();
		//创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中
		WeatherData weatherData = new WeatherData(currentConditions);
		
		//更新天气情况
		weatherData.setData(30, 150, 40);
		
		//天气情况变化
		System.out.println("============天气情况变化=============");
		weatherData.setData(40, 160, 20);

	}
}

结果

***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
============天气情况变化=============
***Today mTemperature: 40.0***
***Today mPressure: 160.0***
***Today mHumidity: 20.0***

观察者模式在JDK的应用

JDK 的 Observable 类就使用了观察者模式在这里插入图片描述

模式角色分析

  • Observable 的作用和地位等价于 我们前面讲过 Subject
  • Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理 Observer 的方法 add… delete … notify…
  • Observer 的作用和地位等价于我们前面讲过的 Observer, 有 update
  • Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

JAVASE提供了java.util.Observable和java.util.Observer来实现观察者模式

代码实现

具体主题(继承java.util.Observable)

//目标对象
public class ConcreteSubject extends Observable {
	
	private int state; 
	
	public void set(int s){
		state = s;  //目标对象的状态发生了改变
		
		setChanged();  //表示目标对象已经做了更改
		notifyObservers(state);  //通知所有的观察者
		
	}

	public int getState() {
		return state;
	}

	public void setState(int state) {
		this.state = state;
	}
	
}

具体观察者(实现java.util.Observer接口)

public class ObserverA implements Observer {

	private int myState;
	
	
	@Override
	public void update(Observable o, Object arg) {
		myState = ((ConcreteSubject)o).getState();
	}


	public int getMyState() {
		return myState;
	}


	public void setMyState(int myState) {
		this.myState = myState;
	}
	
}

客户端调用

public class Client {
	public static void main(String[] args) {
		//创建目标对象Obserable
		ConcreteSubject subject = new ConcreteSubject();
		
		//创建观察者
		ObserverA obs1 = new ObserverA();
		ObserverA obs2 = new ObserverA();
		ObserverA obs3 = new ObserverA();
		
		//将上面三个观察者对象添加到目标对象subject的观察者容器中
		subject.addObserver(obs1);
		subject.addObserver(obs2);
		subject.addObserver(obs3);
		
		//改变subject对象的状态
		subject.set(3000);
		System.out.println("===============状态修改了!");
		//观察者的状态发生了变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());

		subject.set(600);
		System.out.println("===============状态修改了!");
		//观察者的状态发生了变化
		System.out.println(obs1.getMyState());
		System.out.println(obs2.getMyState());
		System.out.println(obs3.getMyState());		
		
	}
}

结果

===============状态修改了!
3000
3000
3000
===============状态修改了!
600
600
600

观察者模式的注意事项

优点

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData 不会修改代码, 遵守了 ocp 原则。
  3. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  4. 目标与观察者之间建立了一套触发机制。

缺点

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

猜你喜欢

转载自blog.csdn.net/qq_42937522/article/details/105193553