观察者模式
Table of Contents
1 观察者模式概述
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图 (Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察 者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个 主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式被称为是模式中的皇后,而且java jdk也对它做了实现,可见该设计模式的重 要位置。在图形化设计的软件中,为了实现视图和事件处理的分离,大多都采用了 Observer模式,比如java的Swing,Flex的ActionScript等。在现实的应用系统中也有好多 应用,比如像当当网、京东商城一类的电子商务网站,如果你对某件商品比较关注,可以 放到收藏架,那么当该商品降价时,系统给您发送手机短信或邮件。这就是观察者模式的 一个典型应用,商品是被观察者,有的叫主体;关注该商品的客户就是观察者。下面的一 个事例将模拟这个应用。
1.1 jdk中观察者模式实现
java jdk中定义了:Observable对象(被观察者)和Observer接口(观察者).它们的关系可总结如下:
- Observable和Observer是一对多的关系,也就是说一旦Observable状态变化,它就要负责 通知所有和它有关系的Observer,然后做相应的改变
- Observable不会主动去通知各个具体的Observer其状态发生了变化,而是提供一个注册 接口供Observer使用,任何一个Observer如果想要被通知,则可以使用这个接口 来注册
- 在Observable中有一个集合和一个状态控制开关,所有注册了通知的Observer会被保 存在这个集合中.这个控制开关就是用来控制Observable是否发生了变化,一旦发生了变 化,就通知所有的Observer更新状态
我们先看一下jdk是如何实现的。被观察者的抽象类java.util.Observable
package java.util;
public class Observable {
private boolean changed = false;
private Vector obs;
/**
* 创建被观察者时就创建一个它持有的观察者列表,注意,这个列表是需要同步的。
*/
public Observable() {
obs = new Vector();
}
/**
* 添加观察者到观察者列表中去
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 删除一个观察者
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
/**
* 通知操作,即被观察者发生变化,通知对应的观察者进行事先设定的操作,这个方法接受一个参数,这个参数一直传到观察者里,以供观察者使用
*/
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
当我们自己的被观察者继承这个Observable类是,我们就自动的获取到被观察者的一切条 件了。很方便是不是,这也是为什么sun要把Observable放到java.util包中的原因,就是 为了方便开发者。
下面我们再看一下观察者的接口java.util.Observer
package java.util;
public interface Observer {
void update(Observable o, Object arg);
}
接口中就只有一个方法,update,方法中有两个参数,Observable和一个object,第一个 参数就是被观察的对象,而第二个参数就得看业务需求了,需要什么就传进去什么。我们 自己的观察者类必须实现这个方法,这样在被观察者调用notifyObservers操作时被观察者 所持有的所有观察者都会执行update操作了,见下图:
2 java中观察者模式应用
2.1 swing事件驱动编程
Swing 框架以事件侦听器的形式广泛利用了观察者模式(也称为发布-订阅模式)。 Swing 组件作为用户交互的目标,在用户与它们交互的时候触发事件;数据模型类在数据 发生变化时触发事件。用这种方式使用观察者,可以让控制器与模型分离,让模型与视图 分离,从而简化 GUI 应用程序的开发。
AWT 和 Swing 组件(例如 JButton 或 JTable)使用观察者模式消除了 GUI 事件生成与 它们在指定应用程序中的语义之间的耦合。类似地,Swing 的模型类,例如 TableModel 和 TreeModel,也使用观察者消除数据模型表示 与视图生成之间的耦合,从而支持相同 数据的多个独立的视图。Swing 定义了 Event 和 EventListener 对象层次结构;可以生 成事件的组件,例如 JButton(可视组件) 或 TableModel(数据模型),提供了 addXxxListener() 和 removeXxxListener() 方法,用于侦听器的登记和取消登记。这些 类负责决定什么时候它们需要触发事件,什么时候确实触发事件,以及什么时候调用所有 登记的侦听器。
为了支持侦听器,对象需要维护一个已登记的侦听器列表,提供侦听器登记和取消登记的 手段,并在适当的事件发生时调用每个侦听器。使用和支持侦听器很容易(不仅仅在 GUI 应用程序中),但是在登记接口的两边(它们是支持侦听器的组件和登记侦听器的组 件)都应当避免一些缺陷。
2.2 监听器和监听器模式
监听器(Listener)是观察者模式的一种实现,监听器模式也就是观察者模式的一种。监听 器模式是对某种共有操作的监控。当此操作执行时对此操作作相应处理。包含的元素:
- 要监控的事件定义Event
- 监控该事件的监听器Listener
- 要监控的事件操作Action
- 监控者
这里举一个例子,说明监听模式的一般实现:
- 首先要定义事件,监听器处理哪些类型的事件,也就是用什么样的事件来触发监听器, 事件的类型很多,这里可以定义一个事件接口来抽象所有事件类型:
/**
* 事件接口,其中事件的类型定义了三种,创建、删除、更新
*/
public interface MyEvent {
public static final String createEvent = "CREATE_EVENT";
public static final String deleteEvent = "DELETE_EVENT";
public static final String updateEvent = "UPDATE_EVENT";
public String getEvent();
}
- 给出一个监听器的接口
/**
* 定义监听器,该监听器只监听MyEvent类型的事件
*/
public interface MyListener {
public void handle(MyEvent myEvent);
}
- 监听器的实现,该实现根据事件类型的不同做不同的处理:
public class MyListenerImpl implements MyListener {
public void handle(MyEvent myEvent) {
if(myEvent.getEvent().equals("CREATE_EVENT")){
System.out.println("myListener get a create event!");
}
else if(myEvent.getEvent().equals("DELETE_EVENT")){
...
}
...
}
}
- 有了监听器的实现,肯定需要一个事件的实现,不过事件的实现类可以是专指某个类型 的pojo,也可以是一个事件类型在使用时被设置的类,下面的实现是第一种,直接定义 了一个create事件的实现类:
public class MyCreateEventImpl implements MyEvent {
private String type="";
public MyCreateEventImpl(){
this.type = MyEvent.createEvent;
}
public String getEvent() {
return this.type;
}
}
- 基础工作都做好后,就可以在想添加监听的类中实施监听功能了:
public class MyCreateAction {
//引入监听器
private MyListener myListener;
//引入事件,用来传给Listener进行处理
private static MyEvent myEvent;
public void setListener(MyListener myListener){
this.myListener = myListener;
}
private void handleListener(MyEvent myEvent){
//触发监听
this.myListener.handle(myEvent);
}
public void execute(){
//设置事件的类型为create
myEvent = new MyCreateEventImpl();
System.out.println("create start!");
this.handleListener(myEvent);
System.out.println("create end!");
}
//调用被监听的类,测试监听效果
public static void main(String[] args) {
MyCreateAction action = new MyCreateAction();
MyListenerImpl myListener = new MyListenerImpl();
//设置监听器的实现
action.setListener(myListener);
action.execute();
}
}
- 输出的结果为:
create start! myListener get a create event! create end!
2.3 Servlet中的listener
我们在web.xml中配置listener的时候就是把一个被观察者放入的观察者的观察对象队列 中,当被观察者触发了注册事件时观察者作出相应的反应。在jsp/servlet中具体的实现 是在web.xml中注册Listener,由Container在特定事件发生时呼叫特定的实现Listener的 类。
总体上说servlet中有主要有3类事件既:Servlet上下文事件、会话事件与请求事件总共 有8个listener接口,我们在web.xml中注册时对应上自己对相应接口的实现类即可, Servlet中的Listener和Event,在JSP 2.0/Servlet 2.4中,共有八个Listener接口,六 个Event类别:
- ServletContextListener接口
- [接口方法] contextInitialized()与 contextDestroyed()
- [接收事件] ServletContextEvent
- [触发场景] 在Container加载Web应用程序时(例如启动 Container之后),会呼叫 contextInitialized(),而当容器移除Web应用程序时,会呼叫contextDestroyed ()方法
- ServletContextAttributeListener
- [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
- [接收事件] ServletContextAttributeEvent
- [触发场景] 若有对象加入为application(ServletContext)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、attributeRemoved()。
- HttpSessionListener
- [接口方法] sessionCreated()与sessionDestroyed ()
- [接收事件] HttpSessionEvent
- [触发场景] 在session (HttpSession)对象建立或被消灭时,会分别呼叫这两个方 法。
- HttpSessionAttributeListener
- [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
- [接收事件] HttpSessionBindingEvent
- [触发场景] 若有对象加入为session(HttpSession)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、 attributeRemoved()。
- HttpSessionActivationListener
- [接口方法] sessionDidActivate()与 sessionWillPassivate()
- [接收事件] HttpSessionEvent
- [触发场景] Activate与Passivate是用于置换对象的动作,当session对象为了资源 利用或负载平衡等原因而必须暂时储存至硬盘或其它储存器时(透 过对象序列化), 所作的动作称之为Passivate,而硬盘或储存器上的session对象重新加载JVM时所采 的动作称之为Activate,所以容 易理解的,sessionDidActivate()与 sessionWillPassivate()分别于Activeate后与将Passivate前呼叫
- ServletRequestListener
- [接口方法] requestInitialized()与 requestDestroyed()
- [接收事件] RequestEvent
- [触发场景] 在request(HttpServletRequest)对象建立或被消灭时,会分别呼叫这两个方法。
- ServletRequestAttributeListener
- [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
- [接收事件] HttpSessionBindingEvent
- [触发场景] 若有对象加入为request(HttpServletRequest)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、 attributeRemoved()
- HttpSessionBindingListener
- [接口方法] valueBound()与valueUnbound()
- [接收事件] HttpSessionBindingEvent
- [触发场景] 实现HttpSessionBindingListener接口的类别,其实例如果被加入至 session(HttpSession)对象的属性中,则会 呼叫 valueBound(),如果被从 session(HttpSession)对象的属性中移除,则会呼叫valueUnbound(),实现 HttpSessionBindingListener接口的类别不需在web.xml中设定。
具体使用方法:在web.xml中添加如下语句:
<listener> <listener-class>com.servlet.listener.YouAchieveListener<\listener-class> <\listener>
其中YouAchieveListener为你实现的某个Listener接口的实现类com.servlet.listener.为 你的包名。
3 c#中的委托和事件
c#的委托和事件便用了观察者模式,事件是主题对象,委托是观察者。其中观察者的实现, c#中使用委托,java中使用接口,委托类似于c语言中的函数指针,它定义了方法的种类:
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegate {
//定义委托,它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
class Program {
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//注意此方法,它接受一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
static void Main(string[] args) {
GreetPeople("Jimmy Zhang", EnglishGreeting);
GreetPeople("张子阳", ChineseGreeting);
Console.ReadKey();
}
}
}
但是c#中的委托实际上是一个类,可以将多个方法赋给同一个委托,或者叫将多个方法绑 定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。在这个例子中, 语法如下:
static void Main(string[] args) {
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("Jimmy Zhang", delegate1);
Console.ReadKey();
}
事件(Event)封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是 protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你 在声明事件时使用的访问符相同。其实没什么不好理解的,声明一个事件不过类似于声明 一个进行了封装的委托类型的变量而已,下面是一个事件示例:
public event GreetingDelegate MakeGreet;
我们进一步看下MakeGreet所产生的代码:
// 对事件的声明 实际是 声明一个私有的委托变量
private GreetingDelegate MakeGreet;
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}
现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管 是不是声明为public,它总是被声明为private。另外,它还有两个方法,分别是 add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。 实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个 方法的访问限制取决于声明事件时的访问限制符。
在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个 方法用于将当前的变量添加到委托链表中,事件就是观察者模式的主题对象,委托就是观 察者模式的观察者对象。
来源:http://www.cnblogs.com/machine/p/3262378.html