观察者模式即发布-订阅模式(Publish/Subscribe):定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式结构图,如下图1-1所示:
图 1-1
二、实例展示
Subjcet 类,可翻译为主题或抽象通知者,一般由一个抽象类或一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
1 abstract class Subject{ 2 private List<Observer> observers = new ArrayList<Observer>(); 3 4 //增加观察者 5 public void Attach(){ 6 observers.Add(observer); 7 } 8 9 //移除观察者 10 public void Detach(){ 11 12 } 13 }
Observer类,抽象观察者,为所有的具体观察者定义了一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。
1 abstract class Observer{ 2 public abstract void Update(); 3 }
ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
1 class ConcreteSubject extends Subject{ 2 private String subjectState; 3 4 public String getSubjectState() { 5 return subjectState; 6 } 7 8 public void setSubjectState(String subjectState) { 9 this.subjectState = subjectState; 10 } 11 }
ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
1 class ConcreteObserver extends Observer{ 2 private String name; 3 private String observerState; 4 private ConcreteSubject subject; 5 6 public ConcreteObserver(ConcreteSubject subject,String name){ 7 this.subject = subject; 8 this.name = name; 9 } 10 11 @override 12 public void Update(){ 13 observerState = subject.SubjectState; 14 System.out.println("观察者"+name+"的新状态是"+observerState); 15 } 16 17 public String getSubjectState() { 18 return subjectState; 19 } 20 21 public void setSubjectState(String subjectState) { 22 this.subjectState = subjectState; 23 } 24 }
客户端代码:
1 static void Main(String[] args){ 2 ConcreteSubject s = new ConcreteSubject(); 3 4 s.Attach(new ConcreteObserver(s,"X")); 5 s.Attach(new ConcreteObserver(s,"Y")); 6 s.Attach(new ConcreteObserver(s,"Z")); 7 8 s.SubjectState = "ABC"; 9 s.Notify(); 10 }
结果显示:
1 观察者X的状态是ABC 2 观察者Y的状态是ABC 3 观察者Z的状态是ABC
2、观察者模式的主要缺点如下:
观察者模式在Java语言中的地位非常重要。在JDK的java.util包中,提供了Observer类及Observer接口,它们构成了JDK对观察者模式的支持。
1 public interface Observer{ 2 void update(Observer o,Object arg); 3 }
Observerable类则为目标类,相比示例中的Publisher类多了并发和NPE方面的考虑。
1 public class Observable { 2 private boolean changed = false; 3 private Vector<Observer> obs; 4 5 /** Construct an Observable with zero Observers. */ 6 7 public Observable() { 8 obs = new Vector<>(); 9 } 10 11 /** 12 * Adds an observer to the set of observers for this object, provided 13 * that it is not the same as some observer already in the set. 14 * The order in which notifications will be delivered to multiple 15 * observers is not specified. See the class comment. 16 * 17 * @param o an observer to be added. 18 * @throws NullPointerException if the parameter o is null. 19 */ 20 public synchronized void addObserver(Observer o) { 21 if (o == null) 22 throw new NullPointerException(); 23 if (!obs.contains(o)) { 24 obs.addElement(o); 25 } 26 } 27 28 /** 29 * Deletes an observer from the set of observers of this object. 30 * Passing <CODE>null</CODE> to this method will have no effect. 31 * @param o the observer to be deleted. 32 */ 33 public synchronized void deleteObserver(Observer o) { 34 obs.removeElement(o); 35 } 36 37 /** 38 * If this object has changed, as indicated by the 39 * <code>hasChanged</code> method, then notify all of its observers 40 * and then call the <code>clearChanged</code> method to 41 * indicate that this object has no longer changed. 42 * <p> 43 * Each observer has its <code>update</code> method called with two 44 * arguments: this observable object and <code>null</code>. In other 45 * words, this method is equivalent to: 46 * <blockquote><tt> 47 * notifyObservers(null)</tt></blockquote> 48 * 49 * @see java.util.Observable#clearChanged() 50 * @see java.util.Observable#hasChanged() 51 * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 52 */ 53 public void notifyObservers() { 54 notifyObservers(null); 55 } 56 57 /** 58 * If this object has changed, as indicated by the 59 * <code>hasChanged</code> method, then notify all of its observers 60 * and then call the <code>clearChanged</code> method to indicate 61 * that this object has no longer changed. 62 * <p> 63 * Each observer has its <code>update</code> method called with two 64 * arguments: this observable object and the <code>arg</code> argument. 65 * 66 * @param arg any object. 67 * @see java.util.Observable#clearChanged() 68 * @see java.util.Observable#hasChanged() 69 * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 70 */ 71 public void notifyObservers(Object arg) { 72 /* 73 * a temporary array buffer, used as a snapshot of the state of 74 * current Observers. 75 */ 76 Object[] arrLocal; 77 78 synchronized (this) { 79 /* We don't want the Observer doing callbacks into 80 * arbitrary code while holding its own Monitor. 81 * The code where we extract each Observable from 82 * the Vector and store the state of the Observer 83 * needs synchronization, but notifying observers 84 * does not (should not). The worst result of any 85 * potential race-condition here is that: 86 * 1) a newly-added Observer will miss a 87 * notification in progress 88 * 2) a recently unregistered Observer will be 89 * wrongly notified when it doesn't care 90 */ 91 if (!changed) 92 return; 93 arrLocal = obs.toArray(); 94 clearChanged(); 95 } 96 97 for (int i = arrLocal.length-1; i>=0; i--) 98 ((Observer)arrLocal[i]).update(this, arg); 99 } 100 101 /** 102 * Clears the observer list so that this object no longer has any observers. 103 */ 104 public synchronized void deleteObservers() { 105 obs.removeAllElements(); 106 } 107 108 /** 109 * Marks this <tt>Observable</tt> object as having been changed; the 110 * <tt>hasChanged</tt> method will now return <tt>true</tt>. 111 */ 112 protected synchronized void setChanged() { 113 changed = true; 114 } 115 116 /** 117 * Indicates that this object has no longer changed, or that it has 118 * already notified all of its observers of its most recent change, 119 * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>. 120 * This method is called automatically by the 121 * <code>notifyObservers</code> methods. 122 * 123 * @see java.util.Observable#notifyObservers() 124 * @see java.util.Observable#notifyObservers(java.lang.Object) 125 */ 126 protected synchronized void clearChanged() { 127 changed = false; 128 } 129 130 /** 131 * Tests if this object has changed. 132 * 133 * @return <code>true</code> if and only if the <code>setChanged</code> 134 * method has been called more recently than the 135 * <code>clearChanged</code> method on this object; 136 * <code>false</code> otherwise. 137 * @see java.util.Observable#clearChanged() 138 * @see java.util.Observable#setChanged() 139 */ 140 public synchronized boolean hasChanged() { 141 return changed; 142 } 143 144 /** 145 * Returns the number of observers of this <tt>Observable</tt> object. 146 * 147 * @return the number of observers of this object. 148 */ 149 public synchronized int countObservers() { 150 return obs.size(); 151 } 152 }
可以使用Observable类以及Observer接口来实现一个微信公众号示例。
增加一个通知类WechatNotice,用于推送通知的传递。
1 public class WechatNotice { 2 private String publisher; 3 private String articleName; 4 5 public WechatNotice(String publisher,String articleName){ 6 this.publisher = publisher; 7 this.articleName = articleName; 8 } 9 10 public String getPublisher() { 11 return publisher; 12 } 13 14 public void setPublisher(String publisher) { 15 this.publisher = publisher; 16 } 17 18 public String getArticleName() { 19 return articleName; 20 } 21 22 public void setArticleName(String articleName) { 23 this.articleName = articleName; 24 } 25 }
然后改写 WeChatClient
和 WeChatAccounts
,分别实现JDK的 Observer
接口和继承 Observable
类。
1 import java.util.Observable; 2 import java.util.Observer; 3 4 public class WeChatClient implements Observer { 5 private String username; 6 public WeChatClient(String username) { 7 this.username = username; 8 } 9 10 public void update(Observable o, Object arg) { 11 //WeChatAccounts weChatAccounts = (WeChatAccounts) o; 12 WechatNotice notice = (WechatNotice) arg; 13 System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName())); 14 15 } 16 }
1 import java.util.Observable; 2 3 public class WeChatAccounts extends Observable { 4 private String name; 5 6 public WeChatAccounts(String name) { 7 this.name = name; 8 } 9 10 public void publishArticles(String articleName, String content) { 11 System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content)); 12 setChanged(); 13 notifyObservers(new WechatNotice(this.getName(), articleName)); 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 }
测试:
1 public class TestMain { 2 public static void main(String[] args){ 3 WeChatAccounts accounts = new WeChatAccounts("飞鹰"); 4 5 WeChatClient user1 = new WeChatClient("张三"); 6 WeChatClient user2 = new WeChatClient("李四"); 7 WeChatClient user3 = new WeChatClient("王五"); 8 9 accounts.addObserver(user1); 10 accounts.addObserver(user2); 11 accounts.addObserver(user3); 12 13 accounts.publishArticles("设计模式 | Java设计模式之观察者模式及其应用场景", "观察者模式的内容..."); 14 15 accounts.deleteObserver(user1); 16 accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "建筑者模式的内容...."); 17 } 18 }
测试结果如下,可以发现结果如示例一致。
1 <飞鹰>微信公众号 发布了一篇推送,文章名称为 <设计模式 | Java设计模式之观察者模式及其应用场景>,内容为 <观察者模式的内容...> 2 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景> 3 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景> 4 用户<张三> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景> 5 6 <飞鹰>微信公众号 发布了一篇推送,文章名称为 <设计模式 | Java设计模式之建筑者模式及其应用场景>,内容为 <建筑者模式的内容....> 7 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景> 8 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>
2、Spring ApplicationContext 事件机制中的观察者模式。
spring的事件机制是从java的事件机制拓展而来,ApplicationContext
中事件处理是由 ApplicationEvent
类和 ApplicationListener
接口来提供的。如果一个Bean实现了 ApplicationListener
接口,并且已经发布到容器中去,每次 ApplicationContext
发布一个 ApplicationEvent
事件,这个Bean就会接到通知
1) ApplicationContext:事件源,其中的 publishEvent()方法用于触发容器事件
2) ApplicationEvent:事件本身,自定义事件需要继承该类,可以用来传递数据
3) ApplicationListener:事件监听器接口,事件的业务逻辑封装在监听器里面
使用 spring 事件机制重新实现示例
1 import org.springframework.context.ApplicationEvent; 2 3 public class WechatNotice extends ApplicationEvent { 4 5 private String publisher; 6 private String articleName; 7 8 public WechatNotice(Object source, String publisher, String articleName) { 9 super(source); 10 this.publisher = publisher; 11 this.articleName = articleName; 12 } 13 14 public String getPublisher() { 15 return publisher; 16 } 17 18 public void setPublisher(String publisher) { 19 this.publisher = publisher; 20 } 21 22 public String getArticleName() { 23 return articleName; 24 } 25 26 public void setArticleName(String articleName) { 27 this.articleName = articleName; 28 } 29 }
1 import org.springframework.context.ApplicationEvent; 2 import org.springframework.context.ApplicationListener; 3 4 public class WeChatClient implements ApplicationListener { 5 6 private String username; 7 8 public WeChatClient(String username) { 9 this.username = username; 10 } 11 12 13 public void onApplicationEvent(ApplicationEvent event) { 14 if (event instanceof WechatNotice) { 15 WechatNotice notice = (WechatNotice) event; 16 System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName())); 17 } 18 } 19 20 public void setUsername(String username) { 21 this.username = username; 22 } 23 24 }
1 import org.springframework.beans.BeansException; 2 import org.springframework.context.ApplicationContext; 3 import org.springframework.context.ApplicationContextAware; 4 5 public class WeChatAccounts implements ApplicationContextAware { 6 7 private ApplicationContext ctx; 8 private String name; 9 10 public WeChatAccounts(String name) { 11 this.name = name; 12 } 13 14 public ApplicationContext getCtx() { 15 return ctx; 16 } 17 18 public void setCtx(ApplicationContext ctx) { 19 this.ctx = ctx; 20 } 21 22 public String getName() { 23 return name; 24 } 25 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 31 this.ctx = applicationContext; 32 } 33 34 public void publishArticles(String articleName, String content) { 35 System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content)); 36 ctx.publishEvent(new WechatNotice(this.name, this.name, articleName)); 37 } 38 }
在 resources 目录下创建 spring.xml
文件,填入下面的内容
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 4 <bean id="WeChatAccounts" class="com.jdk8.webchatListener.WeChatAccounts" scope="prototype"> 5 <constructor-arg name="name" value=""></constructor-arg> 6 </bean> 7 <bean id="WeChatClient1" class="com.jdk8.webchatListener.WeChatClient"> 8 <constructor-arg name="username" value="张三"></constructor-arg> 9 </bean> 10 <bean id="WeChatClient2" class="com.jdk8.webchatListener.WeChatClient"> 11 <constructor-arg name="username" value="李四"></constructor-arg> 12 </bean> 13 <bean id="WeChatClient3" class="com.jdk8.webchatListener.WeChatClient"> 14 <constructor-arg name="username" value="王五"></constructor-arg> 15 </bean> 16 </beans>
测试:
1 import org.springframework.context.ApplicationContext; 2 import org.springframework.context.support.ClassPathXmlApplicationContext; 3 4 public class TestMain { 5 public static void main(String[] args) { 6 ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); 7 8 WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts"); 9 accounts.setName("飞鹰"); 10 accounts.setApplicationContext(context); 11 12 accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "观察者模式的内容..."); 13 } 14 }
输出如下:
1 用户<张三> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景> 2 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景> 3 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>
在此示例中 ApplicationContext
对象的实际类型为 ClassPathXmlApplicationContext
,其中的与 publishEvent
方法相关的主要代码如下:
1 private ApplicationEventMulticaster applicationEventMulticaster; 2 3 public void publishEvent(ApplicationEvent event) { 4 this.getApplicationEventMulticaster().multicastEvent(event); 5 if (this.parent != null) { 6 this.parent.publishEvent(event); 7 } 8 } 9 10 ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException { 11 return this.applicationEventMulticaster; 12 } 13 14 protected void initApplicationEventMulticaster() { 15 ConfigurableListableBeanFactory beanFactory = this.getBeanFactory(); 16 if (beanFactory.containsLocalBean("applicationEventMulticaster")) { 17 this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class); 18 } else { 19 this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); 20 beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster); 21 } 22 23 }
其中的 SimpleApplicationEventMulticaster
如下,multicastEvent
方法主要是通过遍历 ApplicationListener
(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor
来并发执行 ApplicationListener
的 onApplicationEvent
方法,与示例本质上是一致的
1 public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { 2 private Executor taskExecutor; 3 4 public void multicastEvent(final ApplicationEvent event) { 5 Iterator var2 = this.getApplicationListeners(event).iterator(); 6 7 while(var2.hasNext()) { 8 final ApplicationListener listener = (ApplicationListener)var2.next(); 9 Executor executor = this.getTaskExecutor(); 10 if (executor != null) { 11 executor.execute(new Runnable() { 12 public void run() { 13 listener.onApplicationEvent(event); 14 } 15 }); 16 } else { 17 listener.onApplicationEvent(event); 18 } 19 } 20 21 } 22 }
参考:
1、http://laijianfeng.org/2018/10/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E5%8F%8A%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8/
2、大话设计模式。