实例解析观察者模式及其在Java设计模式开发中的运用

一、观察者模式(Observer)的定义:

观察者模式又称为订阅—发布模式,在此模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来事件处理系统。

1、观察者模式的一般结构

首先看下观察者模式的类图描述:

观察者模式的角色如下:

Subject(抽象主题接口):定义了主题类中对观察者列表的一系列操作, 包括增加,删除, 通知等。
Concrete Subject(具体主题类):
Observer(抽象观察者接口):定义了观察者对主题类更新状态接受操作。
ConcreteObserver(具体观察者类):实现观察者接口更新主题类通知等逻辑。
从这个类图可以看出, 主题类中维护了一个实现观察者接口的类列表, 主题类通过这个列表来对观察者进行一系列的增删改操作。观察者类也可以主动调用update方法来了解获取主题类的状态更新信息。

以上的类图所描述的只是基本的观察者模式的思想, 有很多不足。比如作为观察者也可以主动订阅某类主题等。下面的例子将进行一些改动, 以便适用具体的业务逻辑。

2、观察者模式示例

我们构建一个观察者和主题类, 观察者可以主动订阅主题或者取消主题。主题类统一被一个主题管理者所管理。下面给出类图:

Subject:

public interface Subject {
  //注册一个observer
  public void register(Observer observer);
  //移除一个observer
  public void remove(Observer observer);
  //通知所有观察者
  public void notifyObservers();
  //获取主题类要发布的消息
  public String getMessage();
}
ConcerteSubject:
public class MySubject implements Subject {
  private List<Observer> observers;
  private boolean changed;
  private String message;
  //对象锁, 用于同步更新观察者列表
  private final Object mutex = new Object();
  public MySubject() {
    observers = new ArrayList<Observer>();
    changed = false;
  }
  @Override
  public void register(Observer observer) {
    if (observer == null)
      throw new NullPointerException();
      //保证不重复
    if (!observers.contains(observer))
      observers.add(observer);
  }
  @Override
  public void remove(Observer observer) {
    observers.remove(observer);
  }
  @Override
  public void notifyObservers() {
    // temp list
    List<Observer> tempObservers = null;
    synchronized (mutex) {
      if (!changed)
        return;
      tempObservers = new ArrayList<>(this.observers);
      this.changed = false;
    }
    for(Observer obj : tempObservers) {
      obj.update();
    }
  }
  //主题类发布新消息
  public void makeChanged(String message) {
    System.out.println("The Subject make a change: " + message);
    this.message = message;
    this.changed = true;
    notifyObservers();
  }
  @Override
  public String getMessage() {
    return this.message;
  }
}

ConcerteSubject做出更新时, 就通知列表中的所有观察者, 并且调用观察者update方法以实现接受通知后的逻辑。这里注意notifyObservers中的同步块。在多线程的情况下, 为了避免主题类发布通知时, 其他线程对观察者列表的增删操作, 同步块中用一个临时List来获取当前的观察者列表。

SubjectManagement:主题类管理器

public class SubjectManagement {
  //一个记录 名字——主题类 的Map
  private Map<String, Subject> subjectList = new HashMap<String, Subject>();
  public void addSubject(String name, Subject subject) {
    subjectList.put(name, subject);
  }
  public void addSubject(Subject subject) {
    subjectList.put(subject.getClass().getName(), subject);
  }
  public Subject getSubject(String subjectName) {
    return subjectList.get(subjectName);
  }
  public void removeSubject(String name, Subject subject) {
  }
  public void removeSubject(Subject subject) {
  }
  //singleton
  private SubjectManagement() {}
  public static SubjectManagement getInstance() {
    return SubjectManagementInstance.instance;
  }
  private static class SubjectManagementInstance {
    static final SubjectManagement instance = new SubjectManagement();
  }
}

主题类管理器的作用就是在观察者订阅某个主题时, 获取此主题的实例对象。

Observer:

public interface Observer {
  public void update();
  public void setSubject(Subject subject);
}
ConcerteObserver:
public class MyObserver implements Observer {
  private Subject subject;
  // get the notify message from Concentrate Subject
  @Override
  public void update() {
    String message = subject.getMessage();
    System.out.println("From Subject " + subject.getClass().getName()
        + " message: " + message);
  }
  @Override
  public void setSubject(Subject subject) {
    this.subject = subject;
  }
  // subcirbe some Subject
  public void subscribe(String subjectName) {
    SubjectManagement.getInstance().getSubject(subjectName).register(this);
  }
  // cancel subcribe
  public void cancelSubcribe(String subjectName) {
    SubjectManagement.getInstance().getSubject(subjectName).remove(this);
  }
}

测试:我们将主题类和观察者抽象成写者和读者

public class ObserverTest {
  private static MySubject writer;
  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    writer = new MySubject();
    //添加一个名为Linus的作家
    SubjectManagement.getInstance().addSubject("Linus",writer);
  }
  @Test
  public void test() {
    //定义几个读者
    MyObserver reader1 = new MyObserver();
    MyObserver reader2 = new MyObserver();
    MyObserver reader3 = new MyObserver();
    reader1.setSubject(writer);
    reader2.setSubject(writer);
    reader3.setSubject(writer);
    reader1.subscribe("Linus");
    reader2.subscribe("Linus");
    reader3.subscribe("Linus");
    writer.makeChanged("I have a new Changed");
    reader1.update();
  }
}

以上就是观察者模式的小示例。可以看出每个主题类都要维护一个相应的观察者列表, 这里可以根据具体主题的抽象层次进一步抽象, 将这种聚集放到一个抽象类中去实现, 来共同维护一个列表, 当然具体操作要看实际的业务逻辑。

二、Servlet中的Listener

再说Servlet中的Listener之前, 先说说观察者模式的另一种形态——事件驱动模型。与上面提到的观察者模式的主题角色一样, 事件驱动模型包括事件源, 具体事件, 监听器, 具体监听器。
Servlet中的Listener就是典型的事件驱动模型。
JDK中有一套事件驱动的类, 包括一个统一的监听器接口和一个统一的事件源, 源码如下:

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

这是一个标志接口, JDK规定所有监听器必须继承这个接口。

public class EventObject implements java.io.Serializable {
  private static final long serialVersionUID = 5516075349620653480L;
  /**
   * The object on which the Event initially occurred.
   */
  protected transient Object source;
  /**
   * Constructs a prototypical Event.
   *
   * @param  source  The object on which the Event initially occurred.
   * @exception IllegalArgumentException if source is null.
   */
  public EventObject(Object source) {
    if (source == null)
      throw new IllegalArgumentException("null source");
    this.source = source;
  }
  /**
   * The object on which the Event initially occurred.
   *
   * @return  The object on which the Event initially occurred.
   */
  public Object getSource() {
    return source;
  }
  /**
   * Returns a String representation of this EventObject.
   *
   * @return A a String representation of this EventObject.
   */
  public String toString() {
    return getClass().getName() + "[source=" + source + "]";
  }
}

EvenObject是JDK给我们规定的一个统一的事件源。EvenObject类中定义了一个事件源以及获取事件源的get方法。

下面就分析一下Servlet Listener的运行流程。

1、Servlet Listener的组成

目前, Servlet中存在6种两类事件的监听器接口, 具体如下图:

具体触发情境如下表:

2、一个具体的Listener触发过程

我们以ServletRequestAttributeListener为例, 来分析一下此处事件驱动的流程。

首先一个Servlet中, HttpServletRequest调用setAttrilbute方法时, 实际上是调用的org.apache.catalina.connector.request#setAttrilbute方法。 我们看下它的源码:

public void setAttribute(String name, Object value) {
    ...
    //上面的逻辑代码已省略
    // 此处即通知监听者
    notifyAttributeAssigned(name, value, oldValue);
  }

下面是notifyAttributeAssigned(String name, Object value, Object oldValue)的源码

private void notifyAttributeAssigned(String name, Object value,
      Object oldValue) {
    //从容器中获取webAPP中定义的Listener的实例对象
    Object listeners[] = context.getApplicationEventListeners();
    if ((listeners == null) || (listeners.length == 0)) {
      return;
    }
    boolean replaced = (oldValue != null);
    //创建相关事件对象
    ServletRequestAttributeEvent event = null;
    if (replaced) {
      event = new ServletRequestAttributeEvent(
          context.getServletContext(), getRequest(), name, oldValue);
    } else {
      event = new ServletRequestAttributeEvent(
          context.getServletContext(), getRequest(), name, value);
    }
    //遍历所有监听器列表, 找到对应事件的监听器
    for (int i = 0; i < listeners.length; i++) {
      if (!(listeners[i] instanceof ServletRequestAttributeListener)) {
        continue;
      }
      //调用监听器的方法, 实现监听操作
      ServletRequestAttributeListener listener =
        (ServletRequestAttributeListener) listeners[i];
      try {
        if (replaced) {
          listener.attributeReplaced(event);
        } else {
          listener.attributeAdded(event);
        }
      } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);
        // Error valve will pick this exception up and display it to user
        attributes.put(RequestDispatcher.ERROR_EXCEPTION, t);
      }
    }
  }

上面的例子很清楚的看出ServletRequestAttributeListener是如何调用的。用户只需要实现监听器接口就行。Servlet中的Listener几乎涵盖了Servlet整个生命周期中你感兴趣的事件, 灵活运用这些Listenser可以使程序更加灵活。

三、综合示例
举个例子,如果你看过TVB的警匪片,你就知道卧底的工作方式。一般一个警察可能有几个卧底,潜入敌人内部,打探消息,卧底完全靠他的领导的指示干活,领导说几点行动,他必须按照这个时间去执行,如果行动时间改变,他也要立马改变自己配合行动的时间。领导派两个卧底去打入敌人内部,那么领导相当于抽象主题,而督察警官张三这个人派了两个卧底李四和万王五,张三就相当于具体主题,卧底相当于抽象观察者,这两名卧底是李四和王五就是具体观察者,派的这个动作相当于观察者在主题的登记。那么这个类图如下:

利用javaAPI来实现,代码描述如下:

package observer; 

import java.util.List;
import java.util.Observable;
import java.util.Observer;
/**
 *描述:警察张三
 */
public class Police extends Observable { 

  private String time ;
  public Police(List<Observer> list) {
    super();
    for (Observer o:list) {
      addObserver(o);
    }
  }
  public void change(String time){
    this.time = time;
    setChanged();
    notifyObservers(this.time);
  }
}
package observer; 

import java.util.Observable;
import java.util.Observer;
/**
 *描述:卧底A
 */
public class UndercoverA implements Observer { 

  private String time;
  @Override
  public void update(Observable o, Object arg) {
    time = (String) arg;
    System.out.println("卧底A接到消息,行动时间为:"+time);
  } 

}
package observer; 

import java.util.Observable;
import java.util.Observer;
/**
 *描述:卧底B
 */
public class UndercoverB implements Observer {
  private String time;
  @Override
  public void update(Observable o, Object arg) {
    time = (String) arg;
    System.out.println("卧底B接到消息,行动时间为:"+time);
  } 

}
package observer; 

import java.util.ArrayList;
import java.util.List;
import java.util.Observer;
/**
 *描述:测试
 */
public class Client { 

  /**
   * @param args
   */
  public static void main(String[] args) {
    UndercoverA o1 = new UndercoverA();
    UndercoverB o2 = new UndercoverB();
    List<Observer> list = new ArrayList<>();
    list.add(o1);
    list.add(o2);
    Police subject = new Police(list);
    subject.change("02:25");
    System.out.println("===========由于消息败露,行动时间提前=========");
    subject.change("01:05"); 

  } 

}

测试运行结果:

卧底B接到消息,行动时间为:02:25
卧底A接到消息,行动时间为:02:25
===========由于消息败露,行动时间提前=========
卧底B接到消息,行动时间为:01:05
卧底A接到消息,行动时间为:01:05

四、总结

观察者模式定义了对象之间一对多的关系, 当一个对象(被观察者)的状态改变时, 依赖它的对象都会收到通知。可以应用到发布——订阅, 变化——更新这种业务场景中。
观察者和被观察者之间用松耦合的方式, 被观察者不知道观察者的细节, 只知道观察者实现了接口。
事件驱动模型更加灵活,但也是付出了系统的复杂性作为代价的,因为我们要为每一个事件源定制一个监听器以及事件,这会增加系统的负担。

观察者模式的核心是先分清角色、定位好观察者和被观察者、他们是多对一的关系。实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在观察者的构造方法中将被观察者传入、同时将本身注册到被观察者拥有的观察者名单中、即observers这个list中。

1.观察者模式优点:
(1)抽象主题只依赖于抽象观察者
(2)观察者模式支持广播通信
(3)观察者模式使信息产生层和响应层分离

2.观察者模式缺点:
(1)如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
(2)如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知

(0)

相关推荐

  • 深入解析Java设计模式编程中观察者模式的运用

    定义:定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新. 类型:行为类模式 类图: 在软件系统中经常会有这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化.比如,我们要设计一个右键菜单的功能,只要在软件的有效区域内点击鼠标右键,就会弹出一个菜单:再比如,我们要设计一个自动部署的功能,就像eclipse开发时,只要修改了文件,eclipse就会自动将修改的文件部署到服务器中.这两个功能有一个相似的地方,那就是一个对象

  • Java设计模式之观察者模式_动力节点Java学院整理

    定义:定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新. 类型:行为类模式 类图: 在软件系统中经常会有这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化.比如,我们要设计一个右键菜单的功能,只要在软件的有效区域内点击鼠标右键,就会弹出一个菜单:再比如,我们要设计一个自动部署的功能,就像eclipse开发时,只要修改了文件,eclipse就会自动将修改的文件部署到服务器中.这两个功能有一个相似的地方,那就是一个对象

  • java设计模式之观察者模式

    观察者模式又称发布-订阅(Publish/Subscribe)模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己.将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性.我们不希望为了维持一致性而使各类紧密耦合,这样会给维护.扩展和复用都带来不便.观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体. 观察者模式是实际中应用比较广泛的模

  • 用Java设计模式中的观察者模式开发微信公众号的例子

    还记得警匪片上,匪徒们是怎么配合实施犯罪的吗?一个团伙在进行盗窃的时候,总有一两个人在门口把风--如果有什么风吹草动,则会立即通知里面的同伙紧急撤退.也许放风的人并不一定认识里面的每一个同伙:而在里面也许有新来的小弟不认识这个放风的.但是这没什么,这个影响不了他们之间的通讯,因为他们之间有早已商定好的暗号. 呵呵,上面提到的放风者.偷窃者之间的关系就是观察者模式在现实中的活生生的例子. 观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式.GOF给观察者模式如下

  • Java设计模式开发中使用观察者模式的实例教程

    观察者模式是软件设计模式中的一种,使用也比较普遍,尤其是在GUI编程中.关于设计模式的文章,网络上写的都比较多,而且很多文章写的也不错,虽然说有一种重复早轮子的嫌疑,但此轮子非彼轮子,侧重点不同,思路也不同,讲述方式也不近相同. 关键要素 主题: 主题是观察者观察的对象,一个主题必须具备下面三个特征. 持有监听的观察者的引用 支持增加和删除观察者 主题状态改变,通知观察者 观察者: 当主题发生变化,收到通知进行具体的处理是观察者必须具备的特征. 为什么要用这种模式 这里举一个例子来说明,牛奶送奶

  • Java经典设计模式之观察者模式原理与用法详解

    本文实例讲述了Java经典设计模式之观察者模式.分享给大家供大家参考,具体如下: 观察者模式:对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象(被观察). 以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并发生相应的变化. 观察者模式有很多实现方式:该模式必须包含观察者和被观察对象两种角色.观察者和被观察者之间存在"观察"的逻辑关系,当被观察者发生改变的时候,观察者就会观察到这样的变化,发出相应的改变. /** * 观察者接口:观察者,需要用到观察者模式的

  • 学习Java设计模式之观察者模式

    观察者模式:对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象(被观察). 以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并发生相应的变化. 观察者模式有很多实现方式:该模式必须包含观察者和被观察对象两种角色.观察者和被观察者之间存在"观察"的逻辑关系,当被观察者发生改变的时候,观察者就会观察到这样的变化,发出相应的改变. /** * 观察者接口:观察者,需要用到观察者模式的类需实现此接口 */ public interface Observer { pu

  • java设计模式之观察者模式学习

    1.什么是观察者模式 简单情形:有A.B.C.D等四个独立的对象,其中B.C.D这三个对象想在A对象发生改变的第一时间知道这种改变,以便做出相应的响应或者对策. 上面的这种情形,就是观察者模式. 当然可以有多个观察者,多个被观察者. 观察者与被观察者也不是对立的,一个对象可以观察其他对象,也可以被其他对象观察. 2.观察者模式的应用 为了更好的理解什么是观察者模式,下面我举一些可能用到该模式的情形或例子: (1)周期性任务.比如linux中的周期性任务命令crontab命令,win7下的定时关机

  • Java设计模式之观察者模式(Observer模式)介绍

    Java深入到一定程度,就不可避免的碰到设计模式(design pattern)这一概念,了解设计模式,将使自己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广泛,遵循一定的编程模式,才能使自己的代码便于理解,易于交流,Observer(观察者)模式是比较常用的一个模式,尤其在界面设计中应用广泛,而本教程所关注的是Java在电子商务系统中应用,因此想从电子商务实例中分析Observer的应用. 虽然网上商店形式多样,每个站点有自己的特色,但也有其一般的共性,单就"

  • 实例解析观察者模式及其在Java设计模式开发中的运用

    一.观察者模式(Observer)的定义: 观察者模式又称为订阅-发布模式,在此模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知.这通常透过呼叫各观察者所提供的方法来实现.此种模式通常被用来事件处理系统. 1.观察者模式的一般结构 首先看下观察者模式的类图描述: 观察者模式的角色如下: Subject(抽象主题接口):定义了主题类中对观察者列表的一系列操作, 包括增加,删除, 通知等. Concrete Subject(具体主题类): Observer(抽象

  • java微信开发中的地图定位功能

    页面代码: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+&q

  • java 后台开发中model与entity(实体类)的区别说明

    以前在做项目的时候不太了解model与entity的含义,在公司(卓~)项目中学习到了.model的字段>entity的字段,并且model的字段属性可以与entity不一致,model是用于前端页面数据展示的,而entity则是与数据库进行交互做存储用途. 举个例子: 比如在存储时间的类型时,数据库中存的是datetime类型,entity获取时的类型是Date()类型,date型的数据在前端展示的时候必须进行类型转换(转为String类型),在前端的进行类型转换则十分的麻烦,转换成功了代码也

  • Java 后端开发中Tomcat服务器运行不了的五种解决方案

    目录 方法一 方法二 方法三 方法四 方法五 方法一 查看Servers项目是否被关闭或者被删除,Servers是tomcat部署环境的配置项目,我们自己的项目能不能部署在Tomcat服务器上,Servers至关重要.观察该项目是否出于打开的状态: 方法二 查看Servers组件中的tomcat服务器中是否存在太多的被运行执行过的项目,如果存在,需要删除一些不再需要被运行的项目,保留当前需要运行的某个项目即可,因为保留太多的项目会增加tomcat服务器的运行时检索的压力,拖慢运行时间,如果其他项

  • Java项目开发中实现分页的三种方式总结

    目录 前言 使用 1.SpringDataJPA分页 2.MyBatis分页 3.Hutools工具类分页 总结 前言 Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用方式也很简单,可本人在工作中除此以外还用到第三种更方便灵活的分页方式,在这里一同分享给大家. 使用 主要分为SpringDataJPA分页.MyBatis分页.Hutools工具类分页几个部

  • Java Web开发中过滤器和监听器使用详解

    目录 1 Filter 1.1 Filter简介 1.2 Filter的快速入门 1.2.1 创建Filter类 1.2.2 访问index.jsp 1.3 Filter的拦截路径的配置 1.4 过滤器链 1.4.1 过滤器链简介 1.4.2 过滤器链的例子 2 Listener 2.1 概念 2.2 监听器的使用 1 Filter 1.1 Filter简介 Filter表示过滤器,是JavaWeb三大组件(Servlet.Filter.Listener)之一. 过滤器可以把资源的请求拦截下来,

  • 实例解析Java设计模式编程中的适配器模式使用

    平时我们会经常碰到这样的情况,有了两个现成的类,它们之间没有什么联系,但是我们现在既想用其中一个类的方法,同时也想用另外一个类的方法.有一个解决方法是,修改它们各自的接口,但是这是我们最不愿意看到的.这个时候Adapter模式就会派上用场了. Adapter模式也叫适配器模式,是由GoF提出的23种设计模式的一种.Adapter模式是构造型模式之一,通过Adapter模式,可以改变已有类(或外部类)的接口形式. 适配器 模式 有三种方式,一种是对象适配器,一种是类适配器, 一种是接口适配器 以下

  • 实例讲解Java设计模式编程中的OCP开闭原则

    定义:一个软件实体如类.模块和函数应该对扩展开放,对修改关闭. 问题由来:在软件的生命周期内,因为变化.升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试. 解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化.          开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统.开闭原则可能是设计模式六项原则中定义最模糊的一个了,它

  • 解析proxy代理模式在Ruby设计模式开发中的运用

    代理模式 Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上.在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层.如下图: 比如说C和A不在一个服务器上,A要频繁的调用C,我们可以在A上做一个代理类Proxy,把访问C的工作交给Proxy,这样对于A来说,就好像在直接访问C的对

随机推荐