设计模式—观察者模式(Observer)

本文详细介绍了观察者模式,包括其定义、两种实现方式(推模型与拉模型)、实例演示、优缺点及应用场景。通过Java提供的观察者接口实现了一个简单的观察者模式例子,并讨论了如何区别对待观察者以及与状态模式的区别。

一、什么是观察者模式:

观察者模式的定义:

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

这样可能好理解一点:观察者订阅被观察者,被观察者发生改变会通知所有观察者,然后观察者做出相应的行为。

例如:我关注了某明星的微博,该明星每发一条新微博,我就会接到该明星发了一条新微博的通知。

           这里我就是观察者,该明星就是被观察者。

观察者模式的本质: 触发联动

二、观察者模式的两种实现方式:

推模型:    1.推模型是假定目标对象知道观察者需要的具体数据。
                2.推模型会使观察者对象难以复用

拉模型:    1.拉模型是被观察者不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。
                2.拉模型下,update方法的参数是目标对象本,基本上可以适应各种情况的需要

也就是说 拉模型会将被观察者本身传给观察者,而推模型会将被观察者本身的某些属性传给观察者。

三、观察者模式的实例:

 

1、首先,我们需要定义观察者接口,Observer.java

/**
 * 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
 * @author lsy
 *
 */
public interface Observer {
	/** 拉模型
	 * 更新的接口
	 * @param weatherSubject 传入目标对象,方便获取相应的目标对象的状态
	 */
	public void update(WeatherSubject weatherSubject);
	/** 推模型
	 * 更新的接口
	 * @param content   传入目标属性
	 */
	public void update(String content);
}

2、然后,我们需要定义目标对象(被观察者),我们以天气作为目标对象, WeatherSubject .java

/**
 * 目标对象(被观察者),它知道它的观察者,并提供注册(添加)和删除观察者的接口
 */
public class WeatherSubject {
	//用来保存注册的观察者对象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//attach detach notifyObservers
	/**
	 * 把订阅天气的人添加到订阅者列表中
	 * @param observer
	 */
	public void arrach(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);
		}
	}
	/** 推模型
	 * 通知所有已经订阅了天气的人
	 * 只有子类可以调用
	 */
	protected void notifyObservers(String content) {
		for (Observer observer : observers) {
			observer.update(content);
		}
	}
}

3、接下来需要实现具体的观察者ConcreteObserver.java

/**
 * 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * @author lsy
 *
 */
public class ConcreteObserver implements Observer {
	//观察者的名字
	private String observerName;
	//天气内容的情况,这个消息从目标处获取
	private String weatherContent;
	//提醒的内容
	private String remindThing;
	/** 拉模型
	 * 获取目标类的状态同步到观察者的状态中
	 */
	@Override
	public void update(WeatherSubject weatherSubject) {
		weatherContent = ((ConcreteWeatherSubject)weatherSubject).getWeatherContent();
		System.out.println(observerName+"收到了"+weatherContent+","+remindThing);
	}
	/** 推模型
	 * 获取目标类的状态同步到观察者的状态中
	 */
	@Override
	public void update(String content) {
		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;
	}
}

4、实现具体的被观察者 ConcreteWeatherSubject.java

/**
 * 具体的目标对象(被观察者),负责报有关状态存到相应的观察者对象中
 * @author lsy
 *
 */
public class ConcreteWeatherSubject extends WeatherSubject{
	
	//获取天气的内容信息
	private String weatherContent;

	public String getWeatherContent() {
		return weatherContent;
	}

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

5、接下来创建测试类测试一下

public class Client {
	public static void main(String[] args) {
		//1.创建目标
		ConcreteWeatherSubject concreteWeatherSubject = new ConcreteWeatherSubject();
		//2.创建观察者
		ConcreteObserver concreteObserver = new ConcreteObserver();
		concreteObserver.setObserverName("张三");
		concreteObserver.setRemindThing("逛街!");
		
		ConcreteObserver concreteObserver2 = new ConcreteObserver();
		concreteObserver2.setObserverName("李四");
		concreteObserver2.setRemindThing("旅游!");
		//3.注册观察者
		concreteWeatherSubject.arrach(concreteObserver);
		concreteWeatherSubject.arrach(concreteObserver2);
		//4.目标发布天气
		concreteWeatherSubject.setWeatherContent("晴");
		
	}
}

运行结果:

张三收到了晴,逛街!
李四收到了晴,旅游!

 

四、观察者模式的优缺点:

优点:

1.观察者模式实现了观察者和被观察者之间的抽象耦合

原本目标对象在状态发生改变的时候需要直接调用所有的观察者对象,但是抽象出观察者接口之后,目标和观察者之间就只是抽象层面上的耦合,目标只是知道观察者的接口,并不知道观察者的类,从而实现了目标与具体的观察者之间的解耦。


2.观察者模式实现了动态联动

由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者来控制某个动作的联    动范围,从而实现动态联动。


3.观察者模式支持广播通信

目标发送通知给观察者是面向所有注册的观察者,所以目标每次通知的信息就要对所有注册的观察者进行广播,也可以在目标上添加新的方法来限制广播的范围。

缺点:

1.可能会引起无谓的操作

由于观察者模式每次都是广播通信,不管需不需要每个观察者都会被调用update方法。如果观察者不需要执行相应的方法,调用了方法导致误更新那就麻烦了。

五、观察者模式的应用场景:

  1. 当一个抽象模形有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
  2. 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变
  3. 当一个对象必须通知其他的对象,但是你有希望这个对象和其他被他通知的对象是松散耦合的
  4. 例如:QQ好友在线状态(下面用java提供的观察者简单写下)

五、利用Java提供的观察者实现

在java.util包中提供了Observable类,还有一个接口Observer提供了update()方法

使用Java提供的方法有以下四点好处:

  1. 不需要再定义观察者和目标的接口了,JDK帮忙定义了
  2. 具体的目标实现里面不需要再维护观察者的注册信息了,这个在Java中的Observable类里面已经帮忙实现好了。
  3. 触发通知的方式有一点变化,要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能。
  4. 具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了。

下面我们使用Java提供的接口实现观察者模式:

1、直接实现具体的观察者 QQObserver.java

//观察者对象,使自身相关状态与目标相关状态保持一致
public class QQObserver implements Observer {
	/** QQ号码 */
	private String id;
	/** QQ昵称 */
	private String name;
	/** 在线状态 */
	private String state;
	/** 好友昵称 */
	private String friendName;
	/** 好友在线状态 */
	private String friendState;
	
	@Override
	public void update(Observable o, Object arg) {
		this.friendName = ((QQObservable)o).getName();
		this.friendState = ((QQObservable)o).getState();
		System.out.println(name+"的好友"+friendName+"当前的状态更新为:"+friendState);
	}

	public String getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public String getState() {
		return state;
	}

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

	public String getFriendName() {
		return friendName;
	}

	public void setFriendName(String friendName) {
		this.friendName = friendName;
	}

	public String getFriendState() {
		return friendState;
	}

	public void setFriendState(String friendState) {
		this.friendState = friendState;
	}

}

2、实现具体的被观察者 QQObservable.java

//目标对象/被观察者    发布状态并更新到观察者相关状态中
public class QQObservable extends Observable {
	/** QQ昵称 */
	private String name;
	/** QQ在线状态 */
	private String state;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
		//注意再通知之前,在用java中的Observer模式时候,下面这句话必不可少
		this.setChanged();
		//更新状态  主动通知-拉模型
		this.notifyObservers();
	}

	public String getName() {
		return name;
	}

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

3、测试类 Client.java

public class Client {
	public static void main(String[] args) {
		//1.创建目标/被观察者
		QQObservable qq = new QQObservable();
		qq.setName("黄明");
		//2.创建观察者
		QQObserver friendQQ1 = new QQObserver();
		friendQQ1.setName("张三");
		QQObserver friendQQ2 = new QQObserver();
		friendQQ2.setName("李四");
		//3.注册观察者
		qq.addObserver(friendQQ1);
		qq.addObserver(friendQQ2);
		//4.更新在线状态
		qq.setState("离开");
	}
}

运行结果:

李四的好友黄明当前的状态更新为:离开
张三的好友黄明当前的状态更新为:离开

六、区别对待观察者模式:

在被观察者对观察者进行广播的时候进行判断,然后决定对观察者是否广播。

七、与状态模式的不同:

状态模式当状态发生改变时,响应变化的是状态持有者本身,而观察者模式则是被观察者自身发生改变时,通知不同的观察者对执行相应的改变。也就是说状态模式是外部或者内部的状态影响自身,而观察者模式则是影响别人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值