观察者模式
又叫做发布-订阅(Publish/Subscribe)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
观察者模式代码及组成:
Subject类:可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。他把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任意多的观察者。抽象主题提供一个接口,可以增加或删除观察者对象。
/**
* 抽象通知者
*/
public abstract class Subject {
private List<Observer> observers;
public Subject() {
observers = new ArrayList<>();
}
/**
* 添加观察者
* @param observer
*/
public void attach(Observer observer){
observers.add(observer);
}
/**
* 移除观察者
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知观察者
*/
public void notifyObserver(){
observers.forEach(Observer::update);
}
}
Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个update()方法,这个方法叫做更新方法。
/**
* 抽象观察者
*/
public abstract class Observer {
/**
* 观察者被通知时更新状态,和主体状态保持一致
*/
public abstract void update();
}
ConcreteSubject类:具体主题或者具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知,具体主题角色通常用一个具体子类实现。
/**
* 具体通知者
*/
public class ConcreteSubject extends Subject {
/**
* 主题状态
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
}
}
ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
/**
* 具体观察者
*/
public class ConcreteObserver extends Observer {
/**
* 观察者名称
*/
private String name;
/**
* 观察者状态
*/
private String observerState;
/**
* 主体
*/
private ConcreteSubject subject;
public ConcreteObserver(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
@Override
public void update() {
this.observerState = subject.getSubjectState();
System.out.println("主体状态改变,通知观察者" + name + "。观察者新状态为 " + observerState);
}
}
测试代码:
public class ObserverTest {
public static void main(String[] args) {
// 创建主体
ConcreteSubject subject = new ConcreteSubject();
// 添加需要被通知到的观察者
subject.attach(new ConcreteObserver("observer1", subject));
subject.attach(new ConcreteObserver("observer2", subject));
// 改变主题状态,通知观察者
subject.setSubjectState("XYZ");
subject.notifyObserver();
}
}
输出:
主体状态改变,通知观察者observer1。观察者新状态为 XYZ
主体状态改变,通知观察者observer2。观察者新状态为 XYZ
观察者模式特点
使用观察者模式的动机?
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。
什么时候使用观察者模式?
当一个对象的改变需要同时改变其它对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
当一个抽象模型有两个方面,其中一方面依赖另一方面,这时使用观察者模式可以将这两者封装在独立对象中使他们各自独立地改变和复用。
总的来讲,观察者模式所作的工作就是在解除耦合。让耦合的双方都依赖于抽象而不是具体。从而使得各自的变化都不会影响另一边的变化。这就是依赖倒转原则的最佳体现。
观察者使用抽象类VS接口
现实编程中,具体的观察者完全有可能是不相干的类,但他们都需要根据通知者的通知做出相应操作,所以让它们都实现一个接口就可以了。
观察者模式的不足&改进
不足:
尽管已经用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象观察者’,也就是说,万一没有了抽线观察者这样的接口,通知的功能就完不成了。另外就是每个具体观察者,他不一定是更新方法要调用,有可能要调用其它方法,这就是不足的地方
如果通知者和观察者之间根本就互相不知道,由客户端来决定通知谁那就好了。
改进:事件委托实现
可以通过事件委托来实现观察者模式。
EventHandler:事件处理类,相当于主题,用来通知事件方法。
public class EventHandler {
/**
* 观察者集合
*/
private List<Event> eventList;
public EventHandler() {
eventList = new ArrayList<>();
}
/**
* 添加观察者
* @param e
*/
public void attach(Event e){
eventList.add(e);
}
/**
* 移除观察者
* @param e
*/
public void detach(Event e){
eventList.remove(e);
}
/**
* 通知观察者
*/
public void notifyEvent(){
eventList.forEach(Event::invoke);
}
}
Event:事件类,相当于观察者,用来执行具体方法
public class Event {
/**
* 事件类(观察者)
*/
private Object object;
/**
* 方法名称(通知的方法)
*/
private String methodName;
/**
* 方法参数
*/
private Object[] params;
/**
* 参数类型
*/
private Class[] paramTypes;
public Event(Object object, String methodName, Object... params) {
this.object = object;
this.methodName = methodName;
this.params = params;
this.setParamTypes(params);
}
/**
* 设置参数类型
* @param params
*/
private void setParamTypes(Object[] params){
this.paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++){
paramTypes[i] = params[i].getClass();
}
}
/**
* 执行方法
*/
public void invoke(){
Method method = null;
try {
// 1. 获取方法
method = object.getClass().getMethod(this.methodName, this.paramTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
if (method == null){
return;
}
try {
// 2. 执行方法
method.invoke(this.object, this.params);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
Subject类:主题类(通知者)
/**
* 主体(通知者)
*/
public interface Subject {
/**
* 通知方法
*/
void notifyObserver();
}
ConcreteSubject:通知者的具体实现
public class ConcreteSubject implements Subject {
/**
* 主体状态
*/
private String subjectState;
/**
* 事件处理
*/
private EventHandler handler;
public ConcreteSubject() {
handler = new EventHandler();
}
public EventHandler getHandler() {
return handler;
}
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
}
@Override
public void notifyObserver() {
handler.notifyEvent();
}
}
ObserverA:观察者A
public class ObserverA {
private String name;
private ConcreteSubject subject;
public ObserverA(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
public void observerAMethod(){
System.out.println("观察者A name = " + name + " 收到主体状态更新通知,state = " + subject.getSubjectState());
}
}
ObserverB:观察者B
public class ObserverB {
private String name;
private ConcreteSubject subject;
public ObserverB(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
public void observerBMethod(LocalDateTime dateTime){
System.out.println("观察者B name = " + name + " 收到主体状态更新通知,state = " + subject.getSubjectState() + "日期:" + dateTime.toString());
}
}
观察者模式——事件委托测试类
public class ObserverDelegateTest {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ObserverA observerA = new ObserverA("观察者A", subject);
ObserverB observerB = new ObserverB("观察者B", subject);
EventHandler handler = subject.getHandler();
handler.attach(new Event(observerA, "observerAMethod"));
handler.attach(new Event(observerB, "observerBMethod", LocalDateTime.now()));
subject.setSubjectState("subject更新状态了");
subject.notifyObserver();
}
}
输出:
观察者A name = 观察者A 收到主体状态更新通知,state = subject更新状态了
观察者B name = 观察者B 收到主体状态更新通知,state = subject更新状态了日期:2019-06-03T22:33:06.748
事件委托的方式优点
1. 主题(通知者)完全不知道观察者的存在,实现了完全解耦。
2. 一次通知可以执行不同类型的方法。
3. 可扩展性强。