欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-11 16:14:31
一、观察者模式(observer)的定义: 观察者模式又称为订阅—发布模式,在此模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这...

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

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

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

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

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

观察者模式的角色如下:

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

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

2、观察者模式示例

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

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

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种两类事件的监听器接口, 具体如下图:

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

具体触发情境如下表:

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

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

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

利用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)如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知