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

网友投稿 191 2023-07-17


实例解析观察者模式及其在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 observers;

private boolean changed;

private String message;

//对象锁, 用于同步更新观察者列表

private final Object mutex = new Object();

public MySubject() {

observers = new ArrayList();

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 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 subjectList = new HashMap();

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;

}

/**

*xGpHi 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, Ohttp://bject oldValue)的源码

private void notifyAttributeAssigned(String name, Object value,

Object oldValue) {

//从容器中获取webAPP中定义的Listener的实例对象

Object listeners[] = context.getApplicationEventListeners();

if ((listeners == null) || (listeners.length == 0)) {

return;

}

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:页面的缓存与不缓存设置及html页面中meta的作用
下一篇:详细分析Java中String、StringBuffer、StringBuilder类的性能
相关文章

 发表评论

暂时没有评论,来抢沙发吧~