Java设计模式学习笔记------观察者模式详解

观察者模式

观察者模式

观察者模式又称为发布/订阅( Publish/Subscribe )模式,因此我们可以用斗鱼直播的订阅关注来形象的说明:
斗鱼主播开播了会给订阅关注者发开播消息。
你订阅了该该主播,那么只要该主播开播发新消息,就会通知你。
如果你不想再订阅该主播,可以取消订阅,这样,该主播发布开播消息就不会再通知你。
理解其实以上的概念,就可以理解观察者模式,观察者模式中有主题(Subject)和观察者(Observer),分别对应主播和订阅用户(你).观察者模式定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式结构

有个需求: 
黄明是气象局的一名工作者,可以获取最新的天气预报
黄明的女朋友设置了约会的时间地点
黄明的老妈则喜欢逛街,于是设置了购物提醒
这就是观察者收到通知以后,可以各自做出自己的反应


被订阅者黄明Subject可以把观察者女朋友和老妈通过+Attach(o:Observer),+Detach(o:Observer)方法添加,删除观察者Observer
被订阅者Subject状态改变是会通过+Notify()通知观察者Observer调用+Update()自动更新



实现观察者模式

学习观察者模式的通用代码
第一.是目标对象的定义
目标对象WeatherSubject
package WeatherObserverParttern;
import java.util.ArrayList;
import java.util.List;
/**
 * 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
 * @author Administrator
 *
 */
public class WeatherSubject {
    //用来保存注册的观察者对象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//attach detach notifyObserver
	/**
	 * 把订阅天气的人添加到订阅者列表
	 * @param observer
	 */
	public void attach(Observer observer){
		observers.add(observer);
	}
	
	/**
	 * 删除集合中的指定订阅天气的人
	 * @param observer
	 */
	public void detach(Observer observer){
		observers.remove(observer);
	}
	
	/**
	 * 通知所有的订阅天气的人
	 */
	protected void notifyObservers(){
		for(Observer observer:observers)
		{
			observer.update(this);
		}
	}
}
第二.是具体的目标对象的定义
具体的目标对象ConcreteWeatherSubject
package WeatherObserverParttern;
/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者对象中
 * @author Administrator
 *
 */
public class ConcreteWeatherSubject extends WeatherSubject {
    //获取天气的内容信息
	private String weatherContent;

	public String getWeatherContent() {
		return weatherContent;
	}

	public void setWeatherContent(String weatherContent) {
		this.weatherContent = weatherContent;
		//内容有了,说明天气更新了,通知所有订阅的人
		this.notifyObservers();
	}



}
第三.再来看看观察者的接口定义
观察者接口Observer
package WeatherObserverParttern;
/**
 * 这是一个观察者接口,定义一个更新的接口那些在目标发生该表的时候被通知的对象
 * @author Administrator
 *
 */
public interface Observer {

	/**
	 * 更新的接口
	 * @param subject 传入目标对象,方便获取相应的目标对象的状态
	 */
	public void update(WeatherSubject subject);
}
第四.最后来看看观察者的具体实现
具体的观察者对象ConcreteObserver
package WeatherObserverParttern;
/**
 *  具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * @author Administrator
 *
 */
public class ConcreteObserver implements Observer {
	//观察者的名字,是谁收到了这个讯息,黄明的女朋友还是他的老妈
	private String observerName;
	
	//天气内容的情况,这个消息从目标处获取
	private String weatherContent;
	
	//提醒的内容:黄明的女朋友提醒约会,而他老妈则提醒购物
	private String remindThing;
	/**
	 * 获取目标类的状态同步到观察者的状态中
	 */
	@Override
	public void update(WeatherSubject subject) {

		weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent();
		System.out.println(observerName+"收到了"+weatherContent+","+remindThing);
	}
	
	public String getObserverName() {
		return observerName;
	}
	public void setObserverName(String observerName) {
		this.observerName = observerName;
	}
	public String getWeatherContent() {
		return weatherContent;
	}
	public void setWeatherContent(String weatherContent) {
		this.weatherContent = weatherContent;
	}
	public String getRemindThing() {
		return remindThing;
	}
	public void setRemindThing(String remindThing) {
		this.remindThing = remindThing;
	}
}
测试客户端Client
package WeatherObserverParttern;

public class Client {

	public static void main(String[] args) {

		//1 创建目标
        ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
		//2 创建观察者
        ConcreteObserver observerGirl = new ConcreteObserver();
        observerGirl.setObserverName("黄明的女朋友");
        observerGirl.setRemindThing("是我们的第一次约会,地点街心公园,不见不散哦");
        
        ConcreteObserver observerMum = new ConcreteObserver();
        observerMum.setObserverName("老妈");
        observerMum.setRemindThing("是一个购物的好日子,明天去天虹扫货");
		//3 注册观察者
        weather.attach(observerGirl);
        weather.attach(observerMum);
		//4 目标发布消息
        weather.setWeatherContent("明天 天气晴朗,蓝天白云,气温28度");
	}

}

运行结果:
黄明的女朋友收到了明天 天气晴朗,蓝天白云,气温28度,是我们的第一次约会,地点街心公园,不见不散哦
老妈收到了明天 天气晴朗,蓝天白云,气温28度,是一个购物的好日子,明天去天虹扫货

观察者模式详解

认识观察者模式(本质:触发联动)

第一 目标与观察者之间的关系
观察者是典型的一对多的关系,需要注意的是如果观察者只有一个也是可以的。这样就变相的实现了观察者与对象之间一对一的关系。
 一对多的关系
同样的,一个观察者也可以观察多个目标。如果观察者被多个目标通知,更新的方法都是+Update()的话,这样会很麻烦,因为需要接受多个目标的通知。如果是一个+Update()那就需要在方法内部区分。一般情况下,观察者应该为不同的观察者目标定义不同的回调方法,这样实现最简单,不用再在+Update()方法内部区分。

第二 单向依赖
观察者和目标是单向依赖的,只有观察者依赖目标,而不是目标依赖观察者。它们之间的联系的主动权掌握在目标手中,只有目标知道什么时候通知观察者,在整个过程中,观察者始终是被动的等待目标的通知,等待目标传递给它。
第三 命名建议
1.目标接口的定义,建议在名称后面跟Subject
2.观察者接口的定义,建议在名称后面跟Observer
3.观察者接口的更新方法,建议名称为update
第四 触发通知的时机
在实现观察者模式的时候,一定要注意触发通知的时机。一般情况下是在完成了状态维护后触发,因为通知会传递数据,不能够先通知后改数据,这很容易出问题,会导致观察者和目标对象状态的不一致。例如:
//先设置值,再进行的通知
	public void setWeatherContent(String weatherContent) {
		this.weatherContent = weatherContent;
		//内容有了,说明天气更新了,通知所有订阅的人
		this.notifyObservers();
	}
第五 观察者模式的调用顺序示意图
在使用观察者模式时,会很明显的分成两个阶段。第一个是准备阶段,也就是维护观察者和观察者目标关系的阶段,这个阶段的顺序图如下:

另一个是实际运行的阶段,如下:

第六 通知的顺序
从理论上来说,当目标对象的状态变化后,通知所有的观察者的时候顺序是不确定的,因此观察者实现的功能,绝对不能以通知的顺序。也就是说多个观察者之间的顺序是平行的,相互不应该有先后依赖的关系。

观察者模式实现的两种方式(推模型和拉模型)

推模型

目标对象主动向观察者推送目标的详细信息(不管观察者是否需要)
推送的信息通常是目标对象的全部或部分数据(相当于实在广播通信)

拉模型(上面代码的实现)

目标对象在通知观察者的时候,只传递少量信息。
如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据
一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者

推模型的实现

目标对象WeatherSubject
package WeatherObserverPartternNew;
import java.util.ArrayList;
import java.util.List;
/**
 * 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
 * @author Administrator
 *
 */
public class WeatherSubject {
    //用来保存注册的观察者对象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//attach detach notifyObserver
	/**
	 * 把订阅天气的人添加到订阅者列表
	 * @param observer
	 */
	public void attach(Observer observer){
		observers.add(observer);
	}
	
	/**
	 * 删除集合中的指定订阅天气的人
	 * @param observer
	 */
	public void detach(Observer observer){
		observers.remove(observer);
	}
	
	/**
	 * 通知所有的订阅天气的人
	 * @param content
	 */
	protected void notifyObservers(String content){
		for(Observer observer:observers)
		{
			observer.update(content);
		}
	}
}
具体的目标对象ConcreteWeatherSubject
package WeatherObserverPartternNew;
/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者对象中
 * @author Administrator
 *
 */
public class ConcreteWeatherSubject extends WeatherSubject {
    //获取天气的内容信息
	private String weatherContent;

	public String getWeatherContent() {
		return weatherContent;
	}

	public void setWeatherContent(String weatherContent) {
		this.weatherContent = weatherContent;
		//内容有了,说明天气更新了,通知所有订阅的人
		this.notifyObservers(weatherContent);
	}



}
观察者接口Observer
package WeatherObserverPartternNew;
/**
 * 这是一个观察者接口,定义一个更新的接口那些在目标发生该表的时候被通知的对象
 * @author Administrator
 *
 */
public interface Observer {

	/**
	 * 更新的接口
	 * @param Content 天气情况的内容
	 */
	public void update(String Content);
}
具体的观察者对象ConcreteObserver
package WeatherObserverPartternNew;
/**
 *  具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * @author Administrator
 *
 */
public class ConcreteObserver implements Observer {
	//观察者的名字,是谁收到了这个讯息,黄明的女朋友还是他的老妈
	private String observerName;
	
	//天气内容的情况,这个消息从目标处获取
	private String weatherContent;
	
	//提醒的内容:黄明的女朋友提醒约会,而他老妈则提醒购物
	private String remindThing;
	/**
	 * 获取目标类的状态同步到观察者的状态中
	 */
	@Override
	public void update(String content) {

		//weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent();
		weatherContent = content;
		System.out.println(observerName+"收到了"+weatherContent+","+remindThing);
	}
	
	public String getObserverName() {
		return observerName;
	}
	public void setObserverName(String observerName) {
		this.observerName = observerName;
	}
	public String getWeatherContent() {
		return weatherContent;
	}
	public void setWeatherContent(String weatherContent) {
		this.weatherContent = weatherContent;
	}
	public String getRemindThing() {
		return remindThing;
	}
	public void setRemindThing(String remindThing) {
		this.remindThing = remindThing;
	}
}
测试客户端Client(不需要改变)
运行结果:(和拉模型一致)
两个模型的比较
推模型是假定目标对象知道观察者需要的数据
拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值
推模型会使观察者对象难以复用
拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要

利用Java提供的观察者实现

目前为止,我们懂得了观察者模式的应用及其原理的实现,那么如何利用Java自带的观察者来简单的实现观察者。
在java.util包中有个Obserbable类和一个接口Observer 其中定义了update()方法。
Java实现与自己实现的对比
第一 不需要在定义观察者和目标的借口了,JDK帮忙定义了
第二 具体的目标实现里面不需要在维护观察者的注册信息了,这个在Java中的Observable类里面已经帮忙实现好了
第三 触发通知的方式有一点变化,用先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能。
第四 具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了。
具体的目标对象ConcreteWeatherSubject
package WeatherObserverParttern1;

import java.util.Observable;
/**
 * 天气目标的具体实现类
 * @author Administrator
 *
 */
public class ConcreteWeatherSubject extends Observable {

	//天气情况的内容
	private String content;

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
		//天气情况有了,就要通知所有的观察者
		//注意在通知之前,在用Java中的Observer模式时候,下面这句话不可少
		this.setChanged();
		//然后主动通知,这里先用推的方式
		this.notifyObservers(content);
		
		//如果是拉的方式,我们就调用
		//this.notifyObservers();
	}
	
}
具体的观察者对象ConcreteObserver
package WeatherObserverParttern1;

import java.util.Observable;
import java.util.Observer;
/**
 *  具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * @author Administrator
 *
 */
public class ConcreteObserver implements Observer {

	//观察者名称的变量
	private String observerName;
	
	@Override
	public void update(Observable o, Object arg) {

		//第一种是推的方式
		System.out.println(observerName+"收到了消息,目标推送过来的是"+arg);
		
		//第二种是拉的方式
		System.out.println(observerName+"收到了信息,主动到目标对象中去拉,拉的内容是"+((ConcreteWeatherSubject)o).getContent());
	}

	public String getObserverName() {
		return observerName;
	}

	public void setObserverName(String observerName) {
		this.observerName = observerName;
	}

}

测试客户端Client
package WeatherObserverParttern1;
/**
 * 客户端测试类
 * @author Administrator
 *
 */
public class Client {

	public static void main(String[] args) {

		//1 创建目标
        ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
		//2 创建观察者
        ConcreteObserver observerGirl = new ConcreteObserver();
        observerGirl.setObserverName("黄明的女朋友");
        
        ConcreteObserver observerMum = new ConcreteObserver();
        observerMum.setObserverName("黄明的老妈");
		//3 注册观察者
        weather.addObserver(observerGirl);
        weather.addObserver(observerMum);
		//4 目标发布消息
        weather.setContent("明天 天气晴,气温28度");
	}

}
运行结果:
黄明的老妈收到了消息,目标推送过来的是明天 天气晴,气温28度
黄明的老妈收到了信息,主动到目标对象中去拉,拉的内容是明天 天气晴,气温28度
黄明的女朋友收到了消息,目标推送过来的是明天 天气晴,气温28度
黄明的女朋友收到了信息,主动到目标对象中去拉,拉的内容是明天 天气晴,气温28度

观察者模式的优缺点


观察者模式的优点:
第一 观察者模式实现了观察者和目标之间的抽象耦合。
第二 观察者模式实现了动态联动
第三 观察者模式支持广播通信
观察者模式的缺点:
可能会引起无谓的操作

何时使用观察者模式

第一 当以恶搞抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化。

第二 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被改变。

第三 当一个对象必须通知其他的对象,但是你又希望这个对象和其他被它通知的对象是松散耦合的。

区别对待观察者场景问题

需求总结:
第一 黄明的女朋友 只想接收 下雨的天气预报
第二 黄明的老妈想接收 下雨或者下雪的天气预报
解决思路:       
如果天气是晴天,不满足通知黄明的女朋友和黄明的老妈的条件,两者都不通知。

如果天气是下雨,满足通知黄明的女朋友和黄明的老妈的条件,两者都通知。

如果天气是下雪,只满足通知黄明的老妈的条件,只通知黄明的老妈。

示例的实现步骤:
1.定义目标的抽象类和观察者的接口
目标对象WeatherSubject
package Observer1;

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

public abstract class weatherSubject {

	//用来保存注册的观察者对象
	public List<Observer> observers = new ArrayList<Observer>();//定义观察者列表
	
	//添加观察者方法
	public void attach(Observer observer){
		observers.add(observer);
	}
	
	//删除观察者方法
	public void detach(Observer observer){
		observers.remove(observer);
	}
	
	//通知观察者方法(推送消息),需子类完成
	protected abstract void notifyObservers(String content);
}
观察者接口Observer
package Observer1;
/**
 * 定义一个更新的接口方法给那些在目标发生改变的时候的观察者对象的调用
 * @author Administrator
 *
 */
public interface Observer {

	//更新的接口
	public void update(String content);
	
	//设置观察者的名称
	public void setObserverName(String observerName);
	
	//取得观察者名称
	public String getObserverName();
}
2.实现目标的类和观察者的接口
具体的目标对象ConcreteWeatherSubject
package Observer1;

public class ConcreteweatherSubject extends weatherSubject {

    private String content;//定义目标更新内容
	
	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
//		this.setChanged();//将此Observable对象标记为已更改
		this.notifyObservers(content);//通知其所有观察者,并推送消息
//		this.notifyObservers();//通知其所有观察者,并拉消息
	}

	@Override
	protected void notifyObservers(String content) {
		for(Observer observer:observers){
			if(Code.Rain.equals(content)){
				if(Code.Girlfriend.equals(observer.getObserverName())){
					observer.update(content);
				}
				if(Code.Mum.equals(observer.getObserverName())){
					observer.update(content);
				}
			}
			
			if(Code.Snow.equals(content)){
				if(Code.Mum.equals(observer.getObserverName())){
					observer.update(content);
				}
			}
		}
	}
	

}
具体的观察者对象ConcreteObserver
package Observer1;

public class ConcreteObserver implements Observer {

	private String ObserverName;//定义观察者姓名
	
	private String RemainThing;//定义提醒
	
	@Override
	public void update(String content) {

		System.out.println(ObserverName+":"+content+""+RemainThing);
	}

	@Override
	public void setObserverName(String observerName) {

		ObserverName = observerName;
	}

	@Override
	public String getObserverName() {
		// TODO Auto-generated method stub
		return ObserverName;
	}

	public String getRemainThing() {
		return RemainThing;
	}

	public void setRemainThing(String remainThing) {
		RemainThing = remainThing;
	}

	
}
常量类(可定可不定,看自己喜欢)Code
package Observer1;
/**
 * 常量
 * @author Administrator
 *
 */
public class Code {

	/**
	 * 晴天 sunny
	 */
	public static final String Sunny = "晴天";
	/**
	 * 下雨 rain
	 */
	public static final String Rain = "下雨";
	/**
	 * 下雪 snow
	 */
	public static final String Snow = "下雪";
	/**
	 * 女朋友 girlfriend
	 */
	public static final String Girlfriend = "黄明的女朋友";
	/**
	 * 妈妈 mum
	 */
	public static final String Mum = "黄明的老妈";
}
3.进行测试
测试客户端Client
package Observer1;

import Observer1.ConcreteObserver;
import Observer1.ConcreteweatherSubject;

public class Client {

	public static void main(String[] args) {

		//1 创建目标
        ConcreteweatherSubject subject = new ConcreteweatherSubject();
		//2 创建观察者
        ConcreteObserver girlfriend = new ConcreteObserver();
        girlfriend.setObserverName(Code.Girlfriend);
        girlfriend.setRemainThing("不出去玩,在家睡觉");
        
        ConcreteObserver mum = new ConcreteObserver();
        mum.setObserverName(Code.Mum);
        mum.setRemainThing("不出去购物,在家做饭");
		//3 注册观察者
        subject.attach(girlfriend);
        subject.attach(mum);
		//4 目标发布消息
		subject.setContent(Code.Rain);
	}

}
运行结果:
黄明的女朋友:下雨不出去玩,在家睡觉
黄明的老妈:下雨不出去购物,在家做饭


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值