[把你的理性思维慢慢变成条件反射]
本文,我们讲介绍观察者模式,文章主题结构与上文一致。惯例,先来看看我们示例工程的环境:
操作系统:win7 x64
其他软件:eclipse mars,jdk7
-------------------------------------------------------------------------------------------------------------------------------------
经典问题:
EventListener,publisher/subscriber,MQ,Spring MVC等等。思路分析:
要点一:拥有消息发布方,消息接收方两个角色。
要点二:其中一方功能严重依赖于另一方功能。
示例工程:
错误写法:
创建Publisher.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer;
import java.util.ArrayList;
import java.util.List;
public class Publisher {
private List<Subsriber> observers = new ArrayList<Subsriber>();
private String action;
public void attach(Subsriber observer){
observers.add(observer);
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public void notifya(){
for(Subsriber so:observers){
so.update();
}
}
}
创建Subscriber.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer;
public class Subsriber {
private String name;
private Publisher se;
public Subsriber(String name, Publisher se) {
super();
this.name = name;
this.se = se;
}
public void update() {
System.out.println("name:"+name+",action:"+se.getAction());
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer;
public class Window {
public static void main(String[] args) {
Publisher pub = new Publisher();
Subsriber sub1 = new Subsriber("sub1",pub);
Subsriber sub2 = new Subsriber("sub2",pub);
pub.attach(sub1);
pub.attach(sub2);
pub.setAction("start");
pub.notifya();
}
}
错误原因:
Publisher与Subscriber代码耦合度非常高。并且,新增Subscriber时,需要同时修改双方的内容。违反了“开闭原则”,“依赖倒转原则”,“迪米特法则”等。
错误写法(2):
创建Observer.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.one;
public abstract class Observer {
protected String name;
protected Publisher sc;
public Observer(String name, Publisher sc) {
this.name = name;
this.sc = sc;
}
public abstract void update();
}
创建Publisher.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.one;
import java.util.ArrayList;
import java.util.List;
public class Publisher {
private List<Observer> observers = new ArrayList<Observer>();
private String action;
public void attach(Observer observer){
observers.add(observer);
}
public void detach(Observer observer){
observers.remove(observer);
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public void notifya(){
for(Observer so:observers){
so.update();
}
}
}
创建SubObserverA.java,SubObserverB.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.one;
public class SubObserverA extends Observer {
public SubObserverA(String name, Publisher sc) {
super(name,sc);
}
public void update() {
System.out.println("name:"+name+",action:"+sc.getAction()+",JOB-A");
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.one;
public class Window {
public static void main(String[] args) {
Publisher pub = new Publisher();
SubObserverB subob1 = new SubObserverB("subob1",pub);
SubObserverA subob2 = new SubObserverA("subob2",pub);
pub.attach(subob1);
pub.attach(subob2);
pub.setAction("start");
pub.notifya();
}
}<span style="font-family:Microsoft YaHei;font-size:14px;">
</span>
错误原因:
在上一个版本的基础之上,我们将Observer角色进行抽象,在一定程度上满足了Observer角色的变化不会影响Publisher。但是,对于Publisher本身而言,缺少抽象。即,Observer本身也不需要关注具体的消息发布者是谁?在MQ异步消息模型中,经常需要使用的就是这种模型。可能存在多个消息发布者,而无论哪一个消息发布者发出的消息,接受方都需要进行接受并处理。因此,我们还需要进一步优化模型结构。
推荐写法:
创建Observer.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.two;
public abstract class Observer {
protected String name;
protected Publisher sub;
public Observer(String name, Publisher sub) {
this.name = name;
this.sub = sub;
}
public abstract void update();
}
创建Publisher.java文件,具体内容如下:
public interface Publisher {
void attach(Observer observer);
void detach(Observer observer);
void notifya();
void setObserverState(String ObserverState);
String getObserverState();
}
创建ConcretePublisherA.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.two;
import java.util.ArrayList;
import java.util.List;
public class ConcretePublisherA implements Publisher{
private List<Observer> observers = new ArrayList<Observer>();
private String action;
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifya() {
for(Observer so:observers){
so.update();
}
}
public void setObserverState(String oc) {
this.action = oc;
}
public String getObserverState() {
// TODO Auto-generated method stub
return action;
}
}
创建SubObserverA.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.two;
public class SubObserverA extends Observer {
public SubObserverA(String name, Publisher sub) {
super(name,sub);
}
public void update() {
System.out.println("name:"+name+",action:"+sub.getObserverState()+",JOB-A");
}
}
创建Window.java文件,具体内容如下:
public class Window {
public static void main(String[] args) {
Publisher wlw = new ConcretePublisherA();
SubObserverB subob1 = new SubObserverB("s1", wlw);
SubObserverA subob2 = new SubObserverA("n1", wlw);
wlw.attach(subob2);
wlw.attach(subob1);
wlw.setObserverState("stop");
wlw.notifya();
}
}
推荐原因:
这里仅作为功能原始,在实际使用时,会存在共享变量帮助实现消息发布方的抽象。另外,修改之后的代码为面向接口编程,符合“单一职责原则”等。
模式总结:
标准观察者模式UML结构图:
概念总结:
观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个对象。这个主题对象在状态发生变化是,会通知所有观察者对象,使他们能够自动更新自己。组成部分:Subject(抽象观察者合集),ConcreteSubject(具体实现类),Observer(抽象观察者),ConcreteObserver(具体观察者)四部分组成。
模板功能扩展:事件委托处理机制
委托,就是一种引用方法的类型。一旦委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以向其他任何方法一样,具有参数和返回值。委托可以看做是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。一个委托可以搭载多个方法,所有方法被一次唤起。并且,可以是委托对象所搭载的方法并不需要属于同一个类。特别的,委托对象所搭载的所有方法必须具有相同的原型和形式,也就是拥有相同的参数列表和返回值类型。
创建Event.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.three;
import java.lang.reflect.Method;
public class Event {
// 要执行方法的对象
private Object object;
// 要执行的方法名称
private String methodName;
// 要执行方法的参数
private Object[] params;
// 要执行方法的参数类型
private Class[] paramTypes;
public Event() {
}
public Event(Object object, String methodName, Object... args) {
this.object = object;
this.methodName = methodName;
this.params = args;
contractParamTypes(this.params);
}
// 根据参数数组生成参数类型数组
private void contractParamTypes(Object[] params) {
this.paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
this.paramTypes[i] = params[i].getClass();
}
}
public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
// 执行该 对象的该方法
public void invoke() throws Exception {
Method method = object.getClass().getMethod(this.getMethodName(), this.getParamTypes());
if (null == method) {
return;
}
method.invoke(this.getObject(), this.getParams());
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public Class[] getParamTypes() {
return paramTypes;
}
public void setObject(Object object) {
this.object = object;
}
public Object getObject() {
return object;
}
}
创建EventHandler.java文件,具体内容如下;
package com.csdn.ingo.gof_Observer.three;
import java.util.ArrayList;
import java.util.List;
public class EventHandler {
//是用一个List
private List<Event> objects;
public EventHandler(){
objects=new ArrayList<Event>();
}
//添加某个对象要执行的事件,及需要的参数
public void addEvent(Object object,String methodName,Object...args){
objects.add(new Event(object,methodName,args));
}
//添加某个对象要执行的事件,及需要的参数
public void reEvent(Object object,String methodName,Object...args){
objects.add(new Event(object,methodName,args));
}
//通知所有的对象执行指定的事件
public void notifyX() throws Exception{
for(Event e : objects){
e.invoke();
}
}
}
创建ListenerA.java,ListenerB.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.three;
import java.util.Date;
public class ListenerA {
public ListenerA(){
System.out.println("ListenerA start listenering");
}
public void stop(Date date){
System.out.println("ListenerA stop");
}
}
创建Notifier.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.three;
public abstract class Notifier {
private EventHandler eventHandler = new EventHandler();
public EventHandler getEventHandler() {
return eventHandler;
}
public void setEventHandler(EventHandler eventHandler) {
this.eventHandler = eventHandler;
}
public abstract void attach(Object obj, String methodName, Object... args);
public abstract void detach(Object obj, String methodName, Object... args);
public abstract void notifya();
}
创建NotifierA.java,NotifierB.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.three;
public class NotifierA extends Notifier {
@Override
public void attach(Object object, String methodName, Object... args) {
// TODO Auto-generated method stub
System.out.println("add someone");
this.getEventHandler().addEvent(object, methodName, args);
}
@Override
public void detach(Object object, String methodName, Object... args) {
System.out.println("re someone");
this.getEventHandler().reEvent(object, methodName, args);
}
@Override
public void notifya() {
System.out.println("notify all");
try {
this.getEventHandler().notifyX();
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Observer.three;
import java.util.Date;
public class Window {
public static void main(String[] args) {
Notifier n = new NotifierA();
ListenerA la = new ListenerA();
ListenerB lb = new ListenerB();
n.attach(la, "stop", new Date());
n.attach(lb, "stop", new Date());
n.notifya();
}
}
反思:
应用场景:
- 一个模型中存在两个方面,其中一个方面依赖于另一个方便,并且被依赖方具有独立性。
- 被依赖方的数量具有不确定性,即,消息的接受方数量不限。
- 模型中的依赖层级具有不缺定性。即,可能存在多级依赖时,建议使用松耦合的方式进行关联。
优点:
- 实现表现层与数据逻辑层的分离,定义了稳定的消息发布机制,使得观察者的角色不受类型限制。
- Publisher与Subscriber双向解耦,互相不知道其内部细节。
- 支持广播通信,简化一对多的模型设计。
- 满足“开闭原则”“单一职责原则”“依赖倒转原则”等。
缺点:
- 在没有高性能组件的支持下,通过循环通知所有Subscriber,可能会产生一定延迟。
- 依赖层级对于上层不可见,因此,如果发生多级依赖,可能造成程序设计,测试的复杂度上升。
- 完全的松耦合模型,可能会造成两个开发人员团队理解上的困难。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被说了很多遍的设计模式---观察者模式 结束
参考资料:
图书:《大话设计模式》
其他博文:http://blog.csdn.NET/lovelion/article/details/7563445