观察者模式(Observer Pattern)也称发布订阅模式,它是一种在实际开发中经常用到的一种模式。
观察者模式定义:定义对象一种一对多的依赖关系,使得每当一个对象改变状态时,则所依赖它的对象会得到通知并被自动更新。
观察者类图如下:
图1 观察者模式的类图
观察者模式的角色如下:
Clickable.java
/**
* 被观察者接口
* @author HuiSir
*/
public interface Clickable {
//单击
void click();
//添加单击事件的观察者
void addClickableObserver(ClickableObserver observer);
//删除单击事件的观察者
void removeClickableObserver(ClickableObserver observer);
}
ClickableObserver.java
/**
* 观察者接口
* @author HuiSir
*/
public interface ClickableObserver {
//发生单击事件时的操作
void clicked(Clickable clickable);
}
按钮控件,因按钮是可单击的控件,所以Button类实现Clickable接口。代码如下。
Button.java
import java.util.ArrayList;
/**
*Clickable 接口的实现类
* 观察者接口
* @author HuiSir
*/
public class Button implements Clickable {
//存储注册过的单击事件观察者
ArrayList observers = new ArrayList();
//按钮信息
String color;
int x , y ;
@Override
public void click() {
System.out.println("按钮被单击");
//执行所有观察者的事件的处理方法
for(int i = observers.size() - 1 ; i >= 0 ; i--){
observers.get(i).clicked(this);
}
}
@Override
public void addClickableObserver(ClickableObserver observer) {
observers.add(observer);
}
@Override
public void removeClickableObserver(ClickableObserver observer) {
observers.remove(observer);
}
@Override
public String toString(){
return "按钮颜色: " + color + ",坐标" + x + "," + y ;
}
}
ChangeColorObserver.java
/**
* 观察按钮的颜色修改的观察者
* @author HuiSir
*/
public class ChangeColorObserver implements ClickableObserver {
@Override
public void clicked(Clickable clickable) {
Button b = (Button)clickable;
b.color = "红色" ;
}
}
ChangeCoordinateObserver.java
/**
* 观察坐标业务操作的观察者
* @author HuiSir
*/
public class ChangeCoordinateObserver implements ClickableObserver {
@Override
public void clicked(Clickable clickable) {
// TODO Auto-generated method stub
Button b = (Button)clickable;
b.x = 100 ;
b.y = 90 ;
}
}
OtherObserver。java
public class OtherObserver implements ClickableObserver {
@Override
public void clicked(Clickable clickable) {
// TODO Auto-generated method stub
System.out.println("其他操作被执行");
}
}
Test.java
public class Test {
/**
* @author HuiSir
* 测试类
*/
public static void main(String[] args) {
Button button = new Button () ;
button.color = "白色";
button.x = 0 ;
button.y = 0 ;
button.addClickableObserver(new ChangeColorObserver());
button.addClickableObserver(new ChangeCoordinateObserver());
button.addClickableObserver(new OtherObserver());
//button 的click 事件 单击后将触发在button中注册的观察者,然后观察者调用他的方法,从而
//执行其对应的方法。很简单。
button.click();
System.out.println(button);
}
}
Test.java
public class Test {
/**
* @author HuiSir
* 测试类
*/
public static void main(String[] args) {
Button button = new Button () ;
button.color = "白色";
button.x = 0 ;
button.y = 0 ;
button.addClickableObserver(new ChangeColorObserver());
button.addClickableObserver(new ChangeCoordinateObserver());
button.addClickableObserver(new OtherObserver());
//button 的click 事件 单击后将触发在button中注册的观察者,然后观察者调用他的方法,从而
//执行其对应的方法。很简单。
button.click();
System.out.println(button);
}
}
运行结果如下:JDK中有一套事件驱动的类, 包括一个统一的监听器接口和一个统一的事件源, 源码如下:
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
ChangeColorObserver.java
这是一个标志接口, 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方法。具体触发情境如下表:
一个具体的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可以使程序更加灵活。
总结
观察者模式定义了对象之间一对多的关系, 当一个对象(被观察者)的状态改变时, 依赖它的对象都会收到通知。可以应用到发布——订阅, 变化——更新这种业务场景中。
观察者和被观察者之间用松耦合的方式, 被观察者不知道观察者的细节, 只知道观察者实现了接口。
事件驱动模型更加灵活,但也是付出了系统的复杂性作为代价的,因为我们要为每一个事件源定制一个监听器以及事件,这会增加系统的负担。
观察者模式的核心是先分清角色、定位好观察者和被观察者、他们是多对一的关系。实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在观察者的构造方法中将被观察者传入、同时将本身注册到被观察者拥有的观察者名单中、即observers这个list中。
1.观察者模式优点:
(1)抽象主题只依赖于抽象观察者
(2)观察者模式支持广播通信
(3)观察者模式使信息产生层和响应层分离
2.观察者模式缺点:
(1)如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
(2)如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知