一、模式定义:
1、观察者模式(Observer):
定义对象之间一对多的依赖关系,使得每当一个对象改变状态,则所有的依赖于他的对象都会得到通知并且自动更新。该模式也被称为发布--订阅模式、模型--视图模式、源--监听器模式。
2、组成结构:
(1)、Subject(被观察者):定义被观察者必须实现的职责,它必须能够动态的增加、取消观察者。他一般是抽象类或者实现类,主要用于管理观察者并且通知观察者。
(2)、ConcreteSubject(具体的被观察者):定义被观察者自己的业务逻辑,同时定义对那些事件进行通知。
(3)、Observer(观察者):观察者接收到消息后,对接受到的消息进行处理(update)。
(4)、ConcreteObserver(具体的观察者):每个观察在接收到消息后的处理响应是不同的,定义自己的处理逻辑。
3、模式解读:
(1)、该模式有两种不同的角色:观察者、被观察者。
(2)、发消息的是被观察者,收消息的是观察者。
(3)、被观察者与观察者是1:N(N≥1)的关系。
(4)、观察者可以动态的增加或者删除;观察者之间可以无任何的关系。
二、必要性(Why):
描述一个对象与多个对象之间的依赖关系。当一个对象的状态发生变化时,多个对象能够得到通知并且自动更新;发生改变的对象被称为被观察者,而被通知的对象车称为观察者。一个被观察者可以对应多个观察者,这些观察者之间可以没有关联关系,可以根据实际的需要动态的增加/删除观察者,使得系统更加的容易被扩展,这是观察者模式主要的动机。
三、实现(How):
1、抽象的被观察者类:
package com.designpattern.observer;
import java.util.Vector;
/**
* @ClassName: Subject
* @Description:被观察者类
* @author: wanjinYoung
* @date: 2018年8月27日 下午9:15:34
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public abstract class Subject {
//一个观察者组
private Vector<Observer> observers = new Vector<Observer>();
/**
* @Title: addObserver
* @Description:添加一个观察者
* @param: @param o
* @return: void
* @throws
*/
public void addObserver(Observer o) {
observers.add(o);
}
/**
* @Title: removeObserver
* @Description: 删除一个观察者
* @param: @param o
* @return: void
* @throws
*/
public void removeObserver(Observer o) {
observers.remove(o);
}
/**
* @Title: notifyObservers
* @Description:通知所有的观察者
* @param:
* @return: void
* @throws
*/
public void notifyObservers() {
for (Observer o : this.observers) {
o.update();
}
}
}
2、具体的被观察者类:
package com.designpattern.observer;
/**
* @ClassName: ConcreteSubject
* @Description:具体的被观察者类
* @author: wanjinYoung
* @date: 2018年8月27日 下午9:31:15
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class ConcreteSubject extends Subject{
/**
* @Title: doSomthing
* @Description:被观察者在做了一些事情后,通知所有的观察者
* @param:
* @return: void
* @throws
*/
public void doSomthing() {
super.notifyObservers();
}
}
3、抽象的观察者类:
package com.designpattern.observer;
/**
* @ClassName: Observer
* @Description:抽象的观察者
* @author: wanjinYoung
* @date: 2018年8月27日 下午9:35:46
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public abstract class Observer {
//观察者收到消息进行更新
public abstract void update();
}
4、具体的观察者类:
package com.designpattern.observer;
/**
* @ClassName: ConcreteObserver
* @Description:具体的观察者
* @author: wanjinYoung
* @date: 2018年8月27日 下午9:51:36
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class ConcreteObserver extends Observer {
@Override
public void update() {
// TODO Auto-generated method stub
System.out.println("观察者ConcreteObserver接收到消息,进行处理");
}
}
5、测试类:
package com.designpattern.observer;
/**
* @ClassName: Client
* @Description:测试类
* @author: wanjinYoung
* @date: 2018年8月27日 下午9:54:46
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class Client {
public static void main(String[] args) {
//声明被观察者
ConcreteSubject subject = new ConcreteSubject();
//声明观察者
Observer observer = new ConcreteObserver();
//将观察者加入观察者队列
subject.addObserver(observer);
//被观察者行动了 观察者应该都会收到消息
subject.doSomthing();
}
}
6、测试结果:
四、具体应用(Use):
采用Java Swing、Android、QT等技术进行实现图形用户程序(GUI)过程中,事件处理是非常重要且绕不开的编码工作,事件监听器模式是一种最通用且经典的事件处理机制,我们以Java处理事件为例一起来看看,其他如Android、QT的实现方式是类似的,任何支持GUI的操作环境都要不断地监听键盘按键或鼠标点击这样的事件。操作环境将这些事件报告给正在运行的应用程序。如果有事件产生,每个应用程序将决定如何对它们做出响应。
对上述事件处理过程,我们可以进行一个抽象:事件源(例如:按钮或滚动条,对应于被观察者)的本身被注册了事件监听器(对应于观察者),当事件源发生了一个事件(例如:点击或滚动),它会将该事件的相关信息封装在一个相应类型的事件对象中并以通知的形式发送给所有注册的所有事件监听器对象,事件监听器对象可以对该通知进行响应。
理解了上述监听器工作模式后,我们可以利用观察者模式从零开始实现该事件处理框架,以下是具体实现步骤。
1、内置事件的名称。将所有的事件名称以常量的形式定义在该类中。
package com.designpattern.observer.use;
/**
* @ClassName: Event
* @Description:内置QQ常用聊天事件的名称
* @author: wanjinYoung
* @date: 2018年8月28日 上午10:06:57
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class Event {
//文本聊天事件
public static final String MSG_CHAT = "textChat";
//语音聊天事件
public static final String VOICE_CHAT = "voiceCaht";
//视频聊天事件
public static final String VIDEO_CHAT = "videoChat";
}
2、封装事件对象。将事件的名称、数据等信息进行封装,这些事件对象在事件源对象中产生,并在监听器对象中进行使用。
package com.designpattern.observer.use;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: EventObject
* @Description:定义事件对象,并把事件相应的属性进行封装为对象
* @author: wanjinYoung
* @date: 2018年8月28日 上午10:13:05
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class EventObject {
//事件名称
protected String name;
//事件属性集合
protected Map<String, Object> properties;
/**
* @Title: EventObject
* @Description:简单构造函数
* @param: @param name
* @throws
*/
public EventObject(String name) {
// TODO Auto-generated constructor stub
this(name,(Object[]) null);
}
/**
* @Title: EventObject
* @Description:复杂构造函数
* @param: @param name:事件名称
* @param: @param args:属性集合键值对依次存放在 key1,v1,key2,v2,key3,v3,用来传递参数个数可变的,有些类似数组作用
* @throws
*/
public EventObject(String name,Object... args) {
// TODO Auto-generated constructor stub
this.name = name;
properties = new HashMap<String,Object>();
//将传入的数组赋值给Map
if (args != null) {
for (int i = 0; i < args.length; i+=2) {
if (args[i + 1] != null) {
properties.put(String.valueOf(args[i]), args[i + 1]);
}
}
}
}
/**
* @Title: getName
* @Description:获取事件的名称
* @param: @return
* @return: String
* @throws
*/
public String getName() {
return name;
}
/**
* @Title: getProperties
* @Description:获取事件对象的属性列表
* @param: @return
* @return: Map<String,Object>
* @throws
*/
public Map<String, Object> getProperties() {
return properties;
}
/**
* @Title: getProperty
* @Description:根据特定的关键字,获取相应的属性值
* @param: @param key
* @param: @return
* @return: Object
* @throws
*/
public Object getProperty(String key) {
return properties.get(key);
}
}
3、定义事件源对象。实现监听器对象在事件源中的注册、注销;实现特定事件名称与监听器对象进行绑定;实现特定事件发生后,通过调用注册的监听器对象的接口函数进行事件响应。
package com.designpattern.observer.use;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: EventSource
* @Description:定义事件源;管理监听器注册、注销;触发事件后告知监听器对象
* @author: wanjinYoung
* @date: 2018年8月28日 上午10:45:47
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class EventSource {
//用于存放事件源所注册的监听器
//由于特定的监听器往往与特定的事件相关连
//为此,将数据结构存放的内容设计为
//[事件名称1,监听器1,事件名称2,监听器2,...]
protected List<Object> eventListeners = null;
//用于存放事件源
protected Object eventSource;
//设置该事件源是否可以触发外部事件标识
protected boolean eventsEnabled = true;
/**
* @Title: EventSource
* @Description:创建一个简单的事件源
* @param:
* @throws
*/
public EventSource() {
// TODO Auto-generated constructor stub
this(null);
}
/**
* @Title: EventSource
* @Description:按照给定的source创建事件源
* @param: @param source
* @throws
*/
public EventSource(Object source) {
// TODO Auto-generated constructor stub
setEventSource(source);
}
/**
* @Title: getEventSource
* @Description:获取事件源对象
* @param: @return
* @return: Object
* @throws
*/
public Object getEventSource() {
return eventSource;
}
/**
* @Title: setEventSource
* @Description:设置事件源对象
* @param: @param eventSource
* @return: void
* @throws
*/
public void setEventSource(Object eventSource) {
this.eventSource = eventSource;
}
/**
* @Title: isEventsEnabled
* @Description:获取该事件源能否触发外部事件响应
* @param: @return
* @return: boolean
* @throws
*/
public boolean isEventsEnabled() {
return eventsEnabled;
}
/**
* @Title: setEventsEnabled
* @Description: 设置该事件源能否触发外部事件响应
* @param: @param eventsEnabled
* @return: void
* @throws
*/
public void setEventsEnabled(boolean eventsEnabled) {
this.eventsEnabled = eventsEnabled;
}
/**
* @Title: addListener
* @Description:绑定事件名称与相应的事件监听器
* @Description:这样可以保证一旦特定事件发生,相应的的监听器就被触发执行
* @Description:如果没有给定事件的名称,那么该监听器就会被注册到所有的事件中
* @param: @param eventName
* @param: @param listener
* @return: void
* @throws
*/
public void addListener(String eventName,IEventListener listener) {
if (eventListeners == null) {
eventListeners = new ArrayList<Object>();
}
eventListeners.add(eventName);
eventListeners.add(listener);
}
/**
* @Title: removeListener
* @Description: 删除特定的所有事件监听器
* @param: @param listener
* @return: void
* @throws
*/
public void removeListener(IEventListener listener) {
removeListener(listener,null);
}
public void removeListener(IEventListener listener ,String eventName) {
if (eventListeners != null) {
for (int i = eventListeners.size()-2; i > -1; i-=2) {
if (eventListeners.get(i + 1) == listener && (eventName == null ||
String.valueOf(eventListeners.get(i)).equals(eventName))) {
eventListeners.remove(i + 1);
eventListeners.remove(i);
}
}
}
}
/**
* @Title: fireEvent
* @Description:以this为事件源,触发监听器对象
* @param: @param evt
* @return: void
* @throws
*/
public void fireEvent(EventObject evt) {
fireEvent(evt , null);
}
public void fireEvent(EventObject eventObject , Object sender) {
if (eventListeners != null && !eventListeners.isEmpty() && isEventsEnabled()) {
if (sender == null ) {
sender = getEventSource();
}
if (sender == null ) {
sender = this;
}
for (int i = 0; i < eventListeners.size(); i+=2) {
String listen = (String) eventListeners.get(i);
if (listen == null || listen.equals(eventObject.getName())) {
((IEventListener) eventListeners.get(i + 1)).response(
sender, eventObject);
}
}
}
}
}
4、自定义事件监听对象。实现监听接口,通过对事件对象进行解析、处理,完成对事件的响应过程。
package com.designpattern.observer.use;
/**
* @ClassName: IEventListener
* @Description:自定义接口监听接口
* @author: wanjinYoung
* @date: 2018年8月28日 下午1:26:23
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public interface IEventListener {
public void response(Object sender,EventObject evt);
}
package com.designpattern.observer.use;
/**
* @ClassName: AndroidQQ
* @Description:自定义AndroidQQ监听对象,实现了IEventListener监听接口
* @author: wanjinYoung
* @date: 2018年8月28日 下午1:29:45
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class AndroidQQ implements IEventListener {
//对不同的事件进行响应
@Override
public void response(Object sender, EventObject evt) {
//对事件对象进行解析
String msgType = evt.getName();
String msg = "Android设备=>";
if (msgType.equals(Event.MSG_CHAT)) {
msg += evt.getProperty("text");
} else if (msgType.equals(Event.VOICE_CHAT)) {
msg += evt.getProperty("voice");
} else if (msgType.equals(Event.VIDEO_CHAT)) {
msg += evt.getProperty("video");
}
System.out.println(msg);
}
}
package com.designpattern.observer.use;
/**
* @ClassName: IPhoneQQ
* @Description:IphoneQQ 实现接口IEventListener
* @author: wanjinYoung
* @date: 2018年8月28日 下午1:36:55
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class IPhoneQQ implements IEventListener{
//对不同的事件进行响应
@Override
public void response(Object sender, EventObject evt) {
//对事件进行解析
String msgType = evt.getName();
String msg = "IPhone设备=>";
if (msgType.equals(Event.MSG_CHAT)) {
msg += evt.getProperty("text");
} else if (msgType.equals(Event.VOICE_CHAT)) {
msg += evt.getProperty("voice");
} else if (msgType.equals(Event.VIDEO_CHAT)) {
msg += evt.getProperty("video");
}
System.out.println(msg);
}
}
5、自定义事件源对象。实现一些事件的发起,封装事件对象,并将事件对象告知注册的事件监听器对象。
package com.designpattern.observer.use;
/**
* @ClassName: ChatQQ
* @Description:自定义QQ聊天事件源,继承事件源对象EventSource
* @author: wanjinYoung
* @date: 2018年8月28日 下午1:40:13
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class ChatQQ extends EventSource{
//聊天回话名称
private String name;
/**
* @Title: ChatQQ
* @Description:构造函数
* @param: @param name
* @throws
*/
public ChatQQ(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
/**
* @Title: textChat
* @Description:发起文字聊天
* @param:
* @return: void
* @throws
*/
public void textChat() {
EventObject text = new EventObject(Event.MSG_CHAT,"text","text:Hello world!");
this.fireEvent(text);
}
/**
* @Title: voiceChat
* @Description:发起语音聊天
* @param:
* @return: void
* @throws
*/
public void voiceChat() {
EventObject voice = new EventObject(Event.VOICE_CHAT,"voice","voice:Hello world!");
this.fireEvent(voice);
}
/**
* @Title: videoChat
* @Description:发起视频聊天
* @param:
* @return: void
* @throws
*/
public void videoChat() {
EventObject video = new EventObject(Event.VIDEO_CHAT,"video","video:Hello world!");
this.fireEvent(video);
}
}
6、测试。将事件与监听器对象相绑定。可以把相同的事件绑定到不同的事件监听器对象上;同一事件源也可以发起多个事件。
package com.designpattern.observer.use;
/**
* @ClassName: TestEvent
* @Description:测试类
* @author: wanjinYoung
* @date: 2018年8月28日 下午1:50:28
*
* @Copyright: 2018 www.wanjinYoung.com Inc. All rights reserved.
*/
public class TestEvent {
public static void main(String[] args) {
ChatQQ chatQQ = new ChatQQ("QQ会话!");
System.out.println("Android QQ与IPhone QQ上线了...");
//可以把相同的事件绑定到不同的事件监听器对象上
chatQQ.addListener(Event.MSG_CHAT, new IPhoneQQ());
chatQQ.addListener(Event.MSG_CHAT, new AndroidQQ());
chatQQ.addListener(Event.VOICE_CHAT, new IPhoneQQ());
chatQQ.addListener(Event.VOICE_CHAT, new AndroidQQ());
chatQQ.addListener(Event.VIDEO_CHAT, new IPhoneQQ());
chatQQ.addListener(Event.VIDEO_CHAT, new AndroidQQ());
//事件源发生不同的的事件
chatQQ.textChat();
chatQQ.voiceChat();
chatQQ.videoChat();
}
}
7、测试结果。
上述实现了,一个通用的事件监听器模式框架,包括了Event(事件名称定义)、IEventListener(事件监听接口)、EventObject(事件对象)、EventSource(事件源);上述框架可直接应用于实际的开发场景中,只需要:
-
在Event中增加自己的事件名称;
-
implements接口IEventListener,实现自定义事件监听对象;
- extends类EventSource,实现自定义事件源对象。
五、应用场景:
观察者模式在软件开发中应用非常的广泛,如电商网站可以在执行发送操作后给用户发送商品打折信息;在游戏中某位队友牺牲了给所有的成员信提示。总之,涉及到一对多或者一对一的对象交互场景都可使用。
六、总结:
1、观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相应依赖对象皆会得到通知并且自动更新。观察者模式是一种对象行为模式。
2、观察者模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
3、观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
4、观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
5、观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。
6、在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了Java语言对观察者模式的支持。
注:个人学习随记!
代码:https://github.com/youngwanjin/DesignPattern/tree/master/JavaTrying/src/com/designpattern