1.定义
定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
2.类图
- Subject
目标对象。具备功能点:1.管理多个观察者。2.提供对观察者的注册及退订。3.当状态发生改变时,对订阅有效的观察者进行通知。
- Observer
观察者接口。定义目标通知后更新操作
- ConcreteSubject
具体的主题,用来维护自身的状态的变化
- ConcreteObserver
观察者具体实现对象,用来接受目标的通知,并进行后续的操作。
3.深入理解
- 1.目标和观察者的关系按定义区分:一对多。
- 2.单向依赖关系。只有观察者依赖与目标,目标不会依赖与观察者。
建议:
- 目标接口定义,建议在类名称后面加上Subject
- 观察者接口定义,建议在类名称后面加上Observer
- 观察者接口方法,建议命名为update
4.代码
- Subject
/**
* 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
*/
public class Subject {
/**
* 用于保存观察者对象
*/
private List<Observer> observers = new ArrayList<>();
/**
* 注册观察者
*
* @param observer
*/
public void attach(Observer observer) {
observers.add(observer);
}
/**
* 删除观察者对象
*
* @param observer
*/
public void detach(Observer observer) {
observers.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
protected void notifyObservers() {
observers.stream().forEach(observer -> observer.update(this));
}
}
- Observer
/**
* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的后的操作
*/
public interface Observer {
/**
* 更新接口
*
* @param subject
*/
void update(Subject subject);
}
- ConcreteSubject
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者
* 并在自己状态发生改变时,通知各个观察者
*/
public class ConcreteSubject extends Subject {
/**
* 示意目标对象
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
System.out.println("目标对象已更新自身状态为:" + this.subjectState);
this.notifyObservers();
}
}
- ConcreteObserver
/**
* 具体的观察者对象,实现更新方法,使得自身的状态和目标状态保持一致
*/
public class ConcreteObserver implements Observer {
/**
* 示意,观察者对象状态
*/
private String observerState = "呀,我是观察者初始状态!";
@Override
public void update(Subject subject) {
System.out.println("观察者之前的状态为:" + this.observerState);
observerState = ((ConcreteSubject) subject).getSubjectState();
System.out.println("观察者接收到更新的对象状态:" + observerState);
}
}
- ObserverClient
/**
* 调用示例
*/
public class ObserverClient {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer=new ConcreteObserver();
subject.attach(observer);
subject.setSubjectState("121");
}
}
5. 观察者两种通知模式(推模型和拉模型)
5.1 推模型
目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或者部分数据,相当于在广播。
5.2 拉模型
目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。
拉模型的处理方式,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
上面的代码就是典型的拉模型。在具体的主题里,通过update方法把自身传递给具体的观察者。
5.3 如何将上面的拉模型转变为推模型
处理步骤
- 1.主题类的批量通知方法,增加需要通知的参数
- 2.具体观察者的update方法接受具体通知信息
- PushSubject
public class PushSubject {
private List<PushObserver> observers = new ArrayList<>();
/**
* 订阅主题
*
* @param observer
*/
public void attach(PushObserver observer) {
observers.add(observer);
}
/**
* 取消订阅
*
* @param observer
*/
public void detach(PushObserver observer) {
observers.remove(observer);
}
/**
* 拉模型通知所有的注册者
*/
protected void notifyObservers(String content) {
observers.stream().forEach(observer -> {observer.update(content);});
}
}
- ConcretePushSubject
public class ConcretePushSubject extends PushSubject {
/**
* 示意目标对象
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
System.out.println("目标对象已更新自身状态为:" + this.subjectState);
this.notifyObservers(subjectState);
}
}
- PushObserver
public interface PushObserver {
/**
* 更新接口
* 传入更新的内容
* @param content
*/
void update(String content);
}
- ConcretePushObserver
public class ConcretePushObserver implements PushObserver {
@Override
public void update(String content) {
System.out.print("主题推送的信息:" + content);
}
}
- Main
public static void main(String[] args) {
ConcretePushSubject pushSubject=new ConcretePushSubject();
ConcretePushObserver pushObserver=new ConcretePushObserver();
pushSubject.attach(pushObserver);
pushSubject.setSubjectState("1212");
}
5.4 如何选择拉模型与推模型
推模型是假定主题对象知道观察者需要的数据。退模型的一个缺点就是:可能会使得观察者对象难以复用,因为观察者对象的update方法是按需而定义的。ps:为了避免这样的问题,主题对象通常定义一个公用的话题。类比:微信公众号,公众号更新内容,直接推送给订阅的用户。
拉模型是主题对象无法得知观察者具体需要什么数据,干脆把自身对象传递给观察者,让观察者按需提取数据。
6.Java中的观察者
Java语言已经内置好了观察者的实现。
在java.util包中定义了Observable,代表具备可以被观察的能力。也就是我们之前定义的Subject类。目标接口
在java.util包中也定义了一个接口Observer,代表观察者对象接口。里面定义了update方法
6.1好处
- 1.无须额外定义观察者和目标对象的接口,JDK已经内置。
- 2.具体的目标实现里面无须再维护观察者的注册信息,这个在Observable类中已经定义好
- 3.触发方式要先调用setChanged()方法
- 4.具体的观察者的实现里,update方法能够同时支持推模型和拉模型。
6.2代码
- 1.定义主题类
import java.util.Observable;
public class NewPaper extends Observable {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
this.setChanged();
//this.notifyObservers();//拉模型
this.notifyObservers(content);//推模型
}
}
- 2.定义观察者类
import java.util.Observable;
import java.util.Observer;
public class Reader implements Observer {
//读者名称
private String name;
public Reader(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + "收到报纸了,目标推送过来的内容是:" + arg);
//System.out.println(name + "收到报纸了,主动获取到目标更新的内容:" + ((NewPaper) o).getContent());
}
}
- 3.客户端调用
public class Program {
public static void main(String[] args) {
NewPaper paper = new NewPaper();
Reader zhangsan = new Reader("zhangsan");
Reader lisi = new Reader("lisi");
paper.addObserver(zhangsan);
paper.addObserver(lisi);
paper.setContent("iphone 8发布了!!");
}
}
7.观察者优缺点
7.1 优点
- 1.观察者模式实现了观察者与目标之间的抽象耦合
- 2.观察者模式实现了动态联动
- 3.观察者模式实现了广播通信。
7.2 缺点
- 1.可能会引起无谓的操作
因为不管观察者是否需要,都需要调用update方法。
8.观察者的本质
观察者的本质是:触发联动
当修改目标对象的状态时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法。相当于联动调用这些观察者的方法
这个联动还是动态的,可以通过注册和取消注册来控制观察者。同事目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确的联动过来。
9.何时选用观察者
1.当一个抽象模型有两个方面,一个方面依赖于另一个方面的状态改变。
2.在更改一个对象时候,需要同时连带改变其他的对象,而且不知道究竟有多少对象需要被连带改变。
3.当一个对象必须通知其他的对象,但是又希望这个对象和其他被它通知的对象是松散耦合的。
10.与其他设计模式的区别
10.1 观察者模式与状态模式
两者是有相似之处。但也有所不同,观察者重心是触发联动。但是到底决定哪些观察者进行联动,可以配合状态模式。
- 观察者模式
当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作。
- 状态模式
根据不同的状态,选择不同的实现。这个实现类的主要功能就是针对状态相应的操作。
10.2 观察者模式与中介者模式
两个模式可以一起使用。比如观察者模式里面的主题与观察者交互比较复杂,那么就可以一起使用。