设计模式之观察者模式
定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。如微博中一个明星发了一个消息,所有关注的用户都会看到这条消息。
结构
- Observer,观察者接口,定义目标通知时对应的更新方法。
- ConcreteObserver,具体观察者,接收目标通知,并进行相应的业务处理。
- Subject,目标对象,提供观察者注册和退订的方法,当目标状态发生变化时,通知所有已注册的观察者。
- ConcreteSubject,具体的目标对象,维护目标状态,通知所有已注册的观察者。
简单实现
观察者接口
/**
* 观察者接口
*/
public interface Observer {
void update(Subject subject);
}
具体观察者
/**
* 观察者实现
*/
public class ConcreteObserver implements Observer {
@Override
public void update(Subject subject) {
ConcreteSubject concreteSubject = (ConcreteSubject) subject;
System.out.println("ConcreteObserver," + concreteSubject.getSubjectState());
}
}
另一个观察者
/**
* 观察者实现
*/
public class ConcreteObserver2 implements Observer {
@Override
public void update(Subject subject) {
ConcreteSubject concreteSubject = (ConcreteSubject) subject;
System.out.println("ConcreteObserver2," + concreteSubject.getSubjectState());
}
}
目标对象
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers = new ArrayList<>();
/**
* 注册观察者
*/
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);
}
}
}
具体的目标对象
public class ConcreteSubject extends Subject {
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
notifyObservers();
}
}
客户端
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.attach(new ConcreteObserver());
subject.attach(new ConcreteObserver2());
subject.setSubjectState("test");
}
}
其实java中已经提供了 Observer 接口和 Observable 目标类,我们只需要在此基础上实现就可以了。
import java.util.Observable;
import java.util.Observer;
/**
* 观察者实现
*/
public class ConcreteObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
ConcreteSubject concreteSubject = (ConcreteSubject) o;
System.out.println("ConcreteObserver," + concreteSubject.getSubjectState());
}
}
import java.util.Observable;
import java.util.Observer;
/**
* 观察者实现
*/
public class ConcreteObserver2 implements Observer {
@Override
public void update(Observable o, Object arg) {
ConcreteSubject concreteSubject = (ConcreteSubject) o;
System.out.println("ConcreteObserver2," + concreteSubject.getSubjectState());
}
}
具体的目标对象
import java.util.Observable;
public class ConcreteSubject extends Observable {
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
setChanged();
notifyObservers();
}
}
客户端
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.addObserver(new ConcreteObserver());
subject.addObserver(new ConcreteObserver2());
subject.setSubjectState("test");
}
}
推模型和拉模型
观察者模式有两种实现方式,推模型和拉模型。
- 推模型
目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于在广播通信。 - 拉模型
目标对象在通知观察者的时候,只传递少量信息,如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据,
一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
根据上面的描述,我们上面的实现就是拉模型,下面我们实现一下推模型,基于我们自己定义的Observer接口。
/**
* 观察者接口
*/
public interface Observer {
void update(String content);
}
具体观察者
/**
* 观察者实现
*/
public class ConcreteObserver implements Observer {
@Override
public void update(String content) {
System.out.println("ConcreteObserver," + content);
}
}
目标对象
public class Subject {
private List<Observer> observers = new ArrayList<>();
/**
* 注册观察者
*/
public void attach(Observer observer) {
observers.add(observer);
}
/**
* 删除观察者
*/
public void detach(Observer observer) {
observers.remove(observer);
}
/**
* 通知所有观察者
*/
protected void notifyObservers(String content) {
for (Observer observer : observers) {
observer.update(content);
}
}
}
具体目标对象
public class ConcreteSubject extends Subject {
private String subjectState;
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
notifyObservers(subjectState);
}
}
主要的变化在于update方法的参数由Subject(目标对象本身)变成了content(目标对象的详细信息)。
观察者模式在Guava和Spring的实现
Guava中的EventBus
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
public class TestEventBus {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
eventBus.register(new SimpleEventListener());
eventBus.post("this is a test event");
eventBus.post(12);
}
static class SimpleEventListener {
@Subscribe
public void test(String event) {
System.out.println("received1 the event from eventbus: " + event);
}
@Subscribe
public void test2(Integer event) {
System.out.println("received2 the event from eventbus: " + event);
}
}
}
输出结果为
received1 the event from eventbus: this is a test event
received2 the event from eventbus: 12
使用Subscribe注解来注册观察者,通过参数类型来区分不同的目标对象,如String类型,Integer类型,使用post方法发布一条信息,
找到这条消息的类型所对应的观察者列表,通知它们,通过反射执行方法。
Spring中的ApplicationEvent
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
public class TestApplicationEvent {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
context.publishEvent(new OrderSuccessEvent(new Object()));
}
public static class OrderSuccessEvent extends ApplicationEvent {
public OrderSuccessEvent(Object source) {
super(source);
}
}
@Configuration
public static class BeanConfig {
@Bean
public SmsService smsService() {
return new SmsService();
}
@Bean
public SmsService2 smsService2() {
return new SmsService2();
}
}
public static class SmsService implements ApplicationListener<OrderSuccessEvent> {
@Override
public void onApplicationEvent(OrderSuccessEvent event) {
System.out.println("SmsService " + event.getSource());
}
}
public static class SmsService2 {
@EventListener(OrderSuccessEvent.class)
public void sendSms(OrderSuccessEvent event) {
System.out.println("SmsService2 " + event.getSource());
}
}
}
和guava中EventBus使用方法类似,通过ApplicationEvent的具体类型来区分不同的目标对象,
可以通过注解EventListener或接口ApplicationListener两种方式来定义观察者,publishEvent方法发布一个事件,
从IOC容器中查找所有支持此event类型的观察者列表,通知它们。
总结
优点
- 目标对象和观察者之间抽象耦合,目标对象只知道观察者接口,不需要知道具体的观察者实现。
- 通过动态注册、删除观察者,实现动态联动。
- 支持广播通信。
缺点
- 如果目标对象和观察者之间存在循环依赖,可能导致系统崩溃。
- 如果忘记删除不必要的观察者,就会误发送消息,容易导致误操作。
本质
观察者模式的本质是触发联动。在程序运行期间动态的注册和删除观察者,可以变相的实现添加和删除某些功能处理。
使用场景
- 一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,这时就可以使用观察者模式,将这两者封装成观察者和目标对象。
- 在更改一个对象的时候,需要同时连带改变其他的对象,但不知道具体有哪些对象需要被连带改变。
- 当一个对象必须通知其他对象,但希望这个对象和被通知对象之间松散耦合。
参考
大战设计模式【2】—— 观察者模式
设计模式的征途—15.观察者(Observer)模式
设计模式(十八)——观察者模式(JDK Observable源码分析)
观察者模式(Observer模式)详解
Spring事件监听机制
研磨设计模式-书籍