概述
是不是你平时安装程序的时候都会让你选择是否订阅通知,或者我们在网上买东西的物流过程中,每到一个新的进度点都会进行更新,以及通知。其实这里就用到了我们的观察者模式。
概念
观察者模式:定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
比如我们对于天气的订阅,是不是当我们的天气进行变化的时候,应用就会发出通知,告知我们天气的变化。图中的Subject就是天气,Observer就是用户。当天气进行变化的时候Subject中的Notify方法就会被调用,这样Observer中的Update方法就会做出相应的更新。而下面的两个分别是上面的实现类。
观察者模式是单向依赖的,观察者对象的状态是依赖于目标对象的,只有目标对象的状态发生改变,才会改变观察者对象的状态。
观察者模式主要有两种实现方法:推模型和拉模型。
- 推模型:
目标对象主动向观察者推送目标的详细信息,推送的信息通常是目标对象的全部或者部分数据。
- 拉模型:
目标对象在通知观察者的时候只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据,这种实现中一般会把目标对象通过Update方法传递给观察者。
观察者模式实现代码
手动实现
我们根据类图可以知道观察者模式主要是由4个类,Subject,Observer,ConcreteSubject,ConcreteObserver。在Subject中我们需要定义一个集合来储存我们的观察者对象,并且在其中要实现观察者的添加以及删除,并且要完成通知所有注册的观察者对象的方法。
Subject.java(推)
package com.xjh.observer;
import java.util.ArrayList;
import java.util.List;
/**
* 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
* @author Gin
*
*/
public class Subject {
//保存注册的观察者对象
private List<Observer> observers = new ArrayList<Observer>();
//添加观察者
public void attach(Observer observer) {
observers.add(observer);
}
//删除观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//通知所有注册的观察者对象
protected void notifyObservers(String context) {
for(Observer observer : observers) {
observer.update(context);
}
}
}
Subject.java(拉)
package com.xjh.observer;
import java.util.ArrayList;
import java.util.List;
/**
* 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
* @author Gin
*
*/
public class Subject {
//保存注册的观察者对象
private List<Observer> observers = new ArrayList<Observer>();
//添加观察者
public void attach(Observer observer) {
observers.add(observer);
}
//删除观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//通知所有注册的观察者对象
protected void notifyObservers() {
for(Observer observer : observers) {
observer.update(this);
}
}
}
然后定义观察者接口,里面拥有Update方法
Observer.java(推)
package com.xjh.observer;
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author Gin
*
*/
public interface Observer {
/**
* 更新接口
* @param subject 传入对象目标,方便获取相应的目标对象的状态
*/
public void update(String context);
}
Observer.java(拉)
package com.xjh.observer;
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author Gin
*
*/
public interface Observer {
/**
* 更新接口
* @param subject 传入对象目标,方便获取相应的目标对象的状态
*/
public void update(Subject subject);
}
之后就要编写实现类,在Subject的实现类中我们需要定义状态,用来更新,在set方法中调用父类的notifyObservers方法进行对观察者的通知。
ConcreteSubject.java(推)
package com.xjh.observer;
/**
* 具体的目标对象,负责把有关状态存人到相应的观察者对象中
* @author Gin
*
*/
public class ConcreteSubject extends Subject {
//目标对象状态
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObservers(subjectState);
}
}
ConcreteSubject.java(拉)
package com.xjh.observer;
/**
* 具体的目标对象,负责把有关状态存人到相应的观察者对象中
* @author Gin
*
*/
public class ConcreteSubject extends Subject {
//目标对象状态
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObservers();
}
}
然后我们在Observer的实现类中实现Update方法就好了。
ConcreteObserver.java(推)
package com.xjh.observer;
/**
* 具体的观察者对象,实现更新的方法,使自身状态和目标的状态保持一致
* @author Gin
*
*/
public class ConcreteObserver implements Observer {
//观察者的状态
private int id;
private String observerState;
public ConcreteObserver(int id) {
this.id = id;
}
/**
* 获取目标类的状态同步到观察者的状态
*/
@Override
public void update(String context) {
observerState = context;
System.out.println(id+":"+observerState);
}
}
ConcreteObserver.java(拉)
package com.xjh.observer;
/**
* 具体的观察者对象,实现更新的方法,使自身状态和目标的状态保持一致
* @author Gin
*
*/
public class ConcreteObserver implements Observer {
//观察者的状态
private int id;
private String observerState;
public ConcreteObserver(int id) {
this.id = id;
}
/**
* 获取目标类的状态同步到观察者的状态
*/
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject)subject).getSubjectState();
System.out.println(id+":"+observerState);
}
}
当然我们怎么去使用观察者模式呢,这里就要编写一个测试类:
Client.java
package com.xjh.observer;
public class Client {
public static void main(String[] args) {
//创建目标
ConcreteSubject subject = new ConcreteSubject();
//创建观察者
ConcreteObserver one = new ConcreteObserver(1);
ConcreteObserver two = new ConcreteObserver(2);
//注册观察者
subject.attach(one);
subject.attach(two);
//目标发布天气
subject.setSubjectState("明天开会");
//删除观察者
subject.detach(one);
//目标发布天气
subject.setSubjectState("后天开会");
}
}
运行结果如下,满足了我们的要求。
使用Java提供的接口实现
之前为了更好的理解观察者模式,我们已经实现了基本功能,当时Java其实已经提供好了观察者模式的实现。我们先说下使用Java实现与我们自己实现的不同吧。
- 不需要再定义观察者和目标接口
- 具体的目标实现里面不需要再维护观察者的注册信息,这个在Java中的Observable类里面实现好了。
- 触发通知的方式发生改变,需要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能。
- 具体观察者的实现里面,update方法其实已经能同时支持推模型和拉模型
我们使用Java所提供的接口实现的好处就是更加的规范。
我们实现Observable接口,里面已经实现了添加,删除以及通知的方法。
ConcreteSubject.java
package com.xjh.jdkobserver;
import java.util.Observable;
/**
* 天气目标具体实现类
* @author Gin
*
*/
public class ConcreteSubject extends Observable {
private String context;
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
//通知所有观察者
this.setChanged();
this.notifyObservers(context);//推模型
//this.notifyObservers();//拉模型
}
}
因为Observable实现了推拉模型的结合,所以在重写update方法里面有两个参数,分别代表的就是更新内容,以及Observable对象的引用。
ConcreteObserver.java
package com.xjh.jdkobserver;
import java.util.Observable;
import java.util.Observer;
public class ConcreteObserver implements Observer {
//观察者ID
private int id;
public ConcreteObserver(int id) {
this.id = id;
}
@Override
public void update(Observable arg0, Object arg1) {
//推模型,推送过来消息
System.out.println(id+":"+arg1);
//拉模型,主动到目标对象中去拉
//System.out.println(id+":"+((ConcreteSubject)arg0).getContext());
}
}
然后我们在 Main函数中按照步骤实现就好了。
Client.java
package com.xjh.jdkobserver;
public class Client {
public static void main(String[] args) {
//创建目标
ConcreteSubject subject = new ConcreteSubject();
//创建观察者
ConcreteObserver one = new ConcreteObserver(1);
ConcreteObserver two = new ConcreteObserver(2);
ConcreteObserver three = new ConcreteObserver(3);
//注册观察者
subject.addObserver(one);
subject.addObserver(two);
subject.addObserver(three);
//目标发布天气
subject.setContext("明天开会");
//删除观察者
subject.deleteObserver(one);
//目标发布天气
subject.setContext("后天开会");
}
}
我们可以发现他的排序是321,但是我们add的顺序是123,所以可以发现他的插入和读取是类似与栈的形式(我猜的)。
总结
推模型是假定目标对象知道观察者需要的数据,推模型可能会使观察者对象难以复用。
拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值,然而在拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要。
适用场景
- 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
- 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变
- 当一个对象必须通知其他的对象,但是你又希望这个对象和其他的被通知的对象是松散耦合的
优点
- 观察者模式实现了观察者和目标之间的抽象耦合,解除了实现耦合
- 观察者模式实现了动态联动
- 观察者模式支持广播通知