zoukankan      html  css  js  c++  java
  • JAVA设计模式之观察者模式 IT

    一、定义

    观察者模式即发布-订阅模式(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

    三、使用场景

          1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们各自独立地改变和复用。

          2、当一个对象的改变需要同时改变其它对象的时候。

    四、使用总结

           1、观察者模式的主要优点如下:

                 1) 观察者模式可以实现表示层和逻辑层的分离,定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不用的表示层充当具体观察者角色。

                 2) 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者集合,无需了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

                 3) 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。        

                 4) 观察者模式满足"开闭原则"的要求,增加新的具体观察者对象无须修改原有系统代码。在具体观察者和观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

           2、观察者模式的主要缺点如下:

                 1) 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。

                 2) 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

                 3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 

    五、观察者模式的典型应用

            1、JDK提供的观察者接口

                  观察者模式在Java语言中的地位非常重要。在JDK的java.util包中,提供了Observer类及Observer接口,它们构成了JDK对观察者模式的支持。

                  其中的Observer接口为观察者,只有一个update方法。当观察者目标发生变化时被调用,其代码如下:

    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 }

    五、观察者模式不足

                 尽管已经用了依赖倒转的原则,但是"抽象通知者"还是依赖"抽象观察者",也就是说,万一没有了抽象观察者这样的接口,该通知的功能就完不成了。另外就是每个具体观察者,不一定都是"更新"的方法要调用的,需要实现的功能根本风马牛不相及的。要实现该功能,请关注下一节的"java委托功能的实现"。

     参考:

       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、大话设计模式。

  • 相关阅读:
    SVN安装配置与使用
    ext中对json数据的处理解析
    matlab保存数据
    DLL编程总结
    【MFC 】关于对话框中的OnVScroll() 和 OnHScroll
    OpenCV cvReleaseImage把图像怎么样了?
    [code] if (x<0)x=0;else if (x>255)x=255;
    【DM642学习笔记十】DSP优化记录
    DSP日志打印 LOG_printf
    【MFC】MFC文本框中显示浮点数
  • 原文地址:https://www.cnblogs.com/ITBlock/p/10134545.html
Copyright © 2011-2022 走看看