行为型模式
目录
1、观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。
- 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
- 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
- 如何解决:使用面向对象技术,可以将这种依赖关系弱化。
- 关键代码:在抽象类里有一个 ArrayList 存放观察者们。
1.1 观察者模式UML图
1.2 日常生活中看观察者模式
- 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
- 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作
1.3 Java代码实现
下面举一个具体实例,假设上班时间有一部分同事在看股票,一部分同事在看NBA,这时老板回来了,前台通知了部分同事老板回来了,这些同事及时关闭了网页没被发现,而没被通知到的同事被抓了个现行,被老板亲自“通知”关闭网页,UML图如下:
通知者接口
public interface Subject {
//增加
public void attach(Observer observer);
//删除
public void detach(Observer observer);
//通知
public void notifyObservers();
//状态
public void setAction(String action);
public String getAction();
}
观察者
public abstract class Observer {
protected String name;
protected Subject subject;
public Observer(String name, Subject subject) {
this.name = name;
this.subject = subject;
}
public abstract void update();
}
具体通知者,前台Secretary和老板Boss作为具体通知者,实现Subject接口。这里只给出Secretary类的代码,Boss类与之类似。
public class Secretary implements Subject {
//同事列表
private List<Observer> observers = new LinkedList<>();
private String action;
//添加
@Override
public void attach(Observer observer) {
observers.add(observer);
}
//删除
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
//通知
@Override
public void notifyObservers() {
for(Observer observer : observers) {
observer.update();
}
}
//前台状态
@Override
public String getAction() {
return action;
}
@Override
public void setAction(String action) {
this.action = action;
}
}
具体观察者,StockObserver是看股票的同事,NBAObserver是看NBA的同事,作为具体观察者,继承Observer类。这里只给出StockObserver类的代码,NBAObserver类与之类似。
public class StockObserver extends Observer {
public StockObserver(String name, Subject subject) {
super(name, subject);
}
@Override
public void update() {
System.out.println(subject.getAction() + "\n" + name + "关闭股票行情,继续工作");
}
}
前台作为通知者进行通知(Client),前台作为通知者,通知观察者。这里添加adam和tom到通知列表,并从通知列表中删除了adam,测试没在通知列表中的对象不会收到通知。
public class Client {
public static void main(String[] args) {
//前台为通知者
Secretary secretary = new Secretary();
StockObserver observer = new StockObserver("adam", secretary);
NBAObserver observer2 = new NBAObserver("tom", secretary);
//前台通知
secretary.attach(observer);
secretary.attach(observer2);
//adam没被前台通知到,所以被老板抓了个现行
secretary.detach(observer);
//老板回来了
secretary.setAction("小心!Boss回来了!");
//发通知
secretary.notifyObservers();
}
}
运行结果如下,只有tom接收到了通知:
老板作为通知者(Client),通知观察者。这里将tom从老板的通知列表中移除,老板只通知到了adam。
public class Client {
public static void main(String[] args) {
//老板为通知者
Boss boss = new Boss();
StockObserver observer = new StockObserver("adam", boss);
NBAObserver observer2 = new NBAObserver("tom", boss);
//老板通知
boss.attach(observer);
boss.attach(observer2);
//tom没被老板通知到,所以不用挨骂
boss.detach(observer2);
//老板回来了
boss.setAction("咳咳,我大Boss回来了!");
//发通知
boss.notifyObservers();
}
}
运行结果如下,只有adam挨骂了:
当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。
而使用观察者模式的动机在于:将一个系统分割成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性,我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,而观察者模式所做的工作就是在解除耦合。
2、观察者模式在源码中的应用
2.1 Spring源码中观察者模式体现
2.1.1 spring
ApplicationContext 的事件机制就是观察者模式的实现
通过实现 ApplicationListener 的onApplicationEvent 方法。可以监听指定事件。注意实现完毕后要注入要spring容器
监听指定事件
public class ApplicationContextListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("输出contextRefreshedEvent 监听事件内容");
}
}
自定义事件
public class LocalEvent extends ApplicationEvent {
public LocalEvent(Object source) {
super(source);
}
}
监听所有事件
public class ApplicationContextListener2 implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println("事件类型" + applicationEvent.getClass().getName());
}
}
2.1.2 深入源码理解事件监听执行流程
创建容器对象
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
// refresh
refresh();
}
}
org.springframework.context.support.AbstractApplicationContext
refresh方法:去掉了部分源码,重点展示与事件和监听相关的代码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
try {
// 初始化事件广播器
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// 将容器中的监听器放入事件广播器中
// Check for listener beans and register them.
registerListeners();
// Last step: publish corresponding event.
// 初始化完成后刷新
finishRefresh();
}
catch (BeansException ex) {
}
finally {
}
}
}
initApplicationEventMulticaster 方法
/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
// 获取 Bean 工厂
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 判断是否自定义了事件广播器
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 没有自定义事件处理器就创建 SimpleApplicationEventMulticaster 事件广播器
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
registerListeners 方法,将监听器添加到事件广播器中
/**
* Add beans that implement ApplicationListener as listeners.
* Doesn't affect other listeners, which can be added without being beans.
*/
protected void registerListeners() {
// Register statically specified listeners first.
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
// 在 bean 工厂中获取实现了 ApplicationListener 的监听器
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
// 事件广播器循环添加监听器
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
finishRefresh 初始化完成后刷新
/**
* Finish the refresh of this context, invoking the LifecycleProcessor's
* onRefresh() method and publishing the
* {@link org.springframework.context.event.ContextRefreshedEvent}.
*/
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
clearResourceCaches();
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
// 发布 ContextRefreshedEvent 事件
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
根据事件类型来执行相应监听器的监听方法
SimpleApplicationEventMulticaster 中的 multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 获取事件类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取自定义任务执行器
Executor executor = getTaskExecutor();
// 循环监听器,如果自定义任务执行器不存在,就调用invokeListener()方法。看到最后是调用了 listener 的 onApplicationEvent 方法。
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
总结: 启动容器,获取监听器,将监听器添加到事件广播器中。当调用发布事件方法时,由事件广播器对事件进行广播。
2.2 ZooKeeper源码中观察者模式体现
2.2.1 使用ZooKeeper动态监视每一台电脑ip
ZooKeeper作为分布式应用程序的协调服务,其最常用的功能便是观察者模式observe;
平时的1台tomcat无法支持更大的访问量,我们租10台服务器,我们一共有11台机器;
我们做如下分配:
1台作为访问转发,一台作为备用机,其他9台按照3:3:3来分别作为MVC模式的Controller、Service、Dao的服务;
这里我们使用ZooKeeper集群:每一台电脑既作为客户端也作为服务端,当然一台电脑也可以作为ZooKeeper服务端,但是这台电脑坏了怎么办,ZooKeeper集群是为了高可靠性而设计的;
我们用ZooKeeper动态监视每一台电脑ip,来防止突发情况;
import java.util.ArrayList;
import java.util.List;
class TimeSubject {
private List<TimeObserver> observers = new ArrayList<TimeObserver>();
private String time;
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
notifyAllObservers();
}
public void registerObserver(TimeObserver observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (TimeObserver observer : observers) {
observer.action();
}
}
}
abstract class TimeObserver {
protected TimeSubject subject;
public abstract void action();
}
class StudentObserver extends TimeObserver {
public StudentObserver(TimeSubject subject) {
this.subject = subject;
this.subject.registerObserver(this);
}
@Override
public void action() {
if (this.subject.getTime().equals("七点")) {
System.out.println("学生观察到七点了\n准备起床了");
} else if (this.subject.getTime().equals("八点")) {
System.out.println("学生观察到八点了\n准备上课了");
} else {
System.out.println("学生做其他的事");
}
}
}
class TeacherObserver extends TimeObserver {
public TeacherObserver(TimeSubject subject) {
this.subject = subject;
this.subject.registerObserver(this);
}
@Override
public void action() {
if (this.subject.getTime().equals("七点")) {
System.out.println("老师观察到七点了\n准备去学校了");
} else if (this.subject.getTime().equals("八点")) {
System.out.println("老师观察到八点了\n准备讲课了");
} else {
System.out.println("老师做其他的事");
}
}
}
public class ObserverClient {
public static void main(String[] args) {
TimeSubject subject = new TimeSubject();
StudentObserver studentObserver = new StudentObserver(subject);
TeacherObserver teacherObserver = new TeacherObserver(subject);
subject.setTime("七点");
System.out.println("------------------");
subject.setTime("八点");
}
}
2.2.2 ZooKeeper源码
Watcher是Zookeeper用来实现distribute lock, distribute configure, distribute queue等应用的主要手段。要监控data_tree上的任何节点的变化(节点本身的增加,删除,数据修改,以及孩子的变化)都可以在获取该数据时注册一个Watcher,这有很像Listener模式。一旦该节点数据变化,Follower会发送一个notification response,client收到notification响应,则会查找对应的Watcher并回调他们。 有以下接口可以注册Watcher:
- 1. Stat exists(final String path, Watcher watcher)
- 2. Stat exists(String path, boolean watch)
- 3. void exists(String path, boolean watch, StatCallback cb, Object ctx)
- 4. void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)
- 5. byte[] getData(final String path, Watcher watcher, Stat stat)
- 6. byte[] getData(String path, boolean watch, Stat stat)
- 7. void getData(final String path, Watcher watcher, DataCallback cb, Object ctx)
- 8. void getData(String path, boolean watch, DataCallback cb, Object ctx)
- 9. List<string> getChildren(final String path, Watcher watcher)
- 10. List<string> getChildren(String path, boolean watch)
- 11. void getChildren(final String path, Watcher watcher,ChildrenCallback cb, Object ctx)
如果参数需要传递watcher,则可以自己定义Watcher进行回调处理。如果是Boolean型变量,当为true时,则使用系统默认的Watcher,系统默认的Watcher是在zookeeper的构造函数中传递的Watcher。如果Watcher为空或者Boolean变量时为false,则表明不注册Watcher。如果获取数据后不需要关注该数据是否变化,就不需要注册Watcher。上面没有返回值的都是异步调用模式。需要注意的是,一旦Watcher被调用后,将会从map中删除,如果还需要关注数据的变化,需要再次注册。
Watcher的使用与注意事项 :
- 1.Watcher需要每次都要注册。
- 2.Watcher回调之后就销毁如果打算再次回调就需要再次注册
2.3 Dubbo源码中观察者模式体现
Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。
Dubbo中使用观察者模式最典型的例子是
RegistryService
。消费者在初始化的时候回调用subscribe方法,注册一个观察者,如果观察者引用的服务地址列表发生改变,就会通过NotifyListener
通知消费者。此外,Dubbo的InvokerListener
、ExporterListener
也实现了观察者模式,只要实现该接口,并注册,就可以接收到consumer端调用refer和provider端调用export的通知。Dubbo的注册/订阅模型和观察者模式就是天生一对。
3、观察者模式优缺点
3.1 优点
- 建立一套触发机制。
- Subject和Observer之间是松偶合的,分别可以各自独立改变。
- Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。
- 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。观察者和被观察者是抽象耦合的。
3.2 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
3.3 使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
3.4 注意事项
- 1、JAVA 中已经有了对观察者模式的支持类。
- 2、避免循环引用。
- 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
参考文章:
https://www.cnblogs.com/adamjwh/p/10913660.html
https://blog.csdn.net/che1165338661/article/details/109485362
https://blog.csdn.net/qq_44104303/article/details/110522168