1.观察者模式的概念
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
2.观察者模式的结构图
Subject类:抽象通知者,它把所有对观察者对象的引用保存在一个聚集里,每个通知者都可以有任何数量的观察者。抽象通知者提供一个接口,可以增加和删除观察者对象。
ConcreteSubject类:具体通知者,将有关状态存入具体观察者对象;在具体通知者的内部状态改变时,给所有登记过的观察者发出通知。具体通知角色通常用一个具体子类实现。
Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update方法,这个方法叫做更新方法。
ConcreteObserver类:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
由于下面的例子和观察者模式的这个结构图的代码实现类似,下面将举一个小例子来说明观察者模式如何去实现。
3.观察者模式的例子
假设在一个公司里面,当老板出门去办事,这个时候公司里面就是老虎不在家,猴子称霸王。公司里面的有些员工就开始看自己的大盘炒股票,看NBA球赛等等。但是这样玩着好像心里一直都会悬着,他们就去跟前台的MM商量了一下,老板回来的时候给他们通知一声,然后他们停止娱乐活动,开始进入工作状态。
在这个例子当中就是一个典型的观察者模式,其中前台的MM和老板都是具体的通知者(ConcreteSubject),而那些看股票,看NBA的人就是具体的观察者。通知者的公共行为抽象出来就成了抽象通知者(Subject),而具体的抽象出来就成了抽象观察者(Observer)。它们的关系如下图的UML结构图所示:
当我们画出这个结构图的时候,代码的构成也十分清晰了,下面我们来使用代码实现这个小例子:
Subject类(抽象通知者):
package com.jxs.observer;
/**
* Created by jiangxs on 2018/5/8.
*
* 通知者接口
*/
public interface Subject {
/**
* 添加观察者
* @param observer
* */
void attach(Observer observer);
/**
* 移除观察者
* @param observer
* */
void detach(Observer observer);
/**
* 通知某个观察者
* @param observer
* @param info
* */
void notifyObserver(Observer observer,Object info);
/**
* 通知所有观察者
* @param info
*/
void notifyAllObserver(Object info);
}
Secretary类(ConcreteSubject):
package com.jxs.observer;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jiangxs on 2018/5/8.
*/
public class Secretary implements Subject {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
// 前台状态
private String subjectState;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyAllObserver(Object info) {
for (Observer observer : observers) {
observer.update(this, info);
}
}
@Override
public void notifyObserver(Observer observer, Object info) {
observer.update(this, info);
}
}
Boss类(ConcreteSubject):
package com.jxs.observer;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jiangxs on 2018/5/8.
*/
public class Boss implements Subject {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
// 老板状态
private String subjectState;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyAllObserver(Object info) {
for (Observer observer : observers) {
observer.update(this, info);
}
}
@Override
public void notifyObserver(Observer observer, Object info) {
observer.update(this, info);
}
}
Observer:
package com.jxs.observer;
/**
* Created by jiangxs on 2018/5/8.
*
* 观察者接口
*/
public interface Observer {
/**
* 观察者根据具体情况进行相应的应对措施
* @param subject
* @param info
* */
void update(Subject subject, Object info);
}
StockObserver类(ConcreteObserver):
package com.jxs.observer;
/**
* Created by jiangxs on 2018/5/8.
*/
public class StockObserver implements Observer {
@Override
public void update(Subject subject, Object info) {
System.out.println(this.getClass().getSimpleName()
+","+info+","
+"赶紧关闭炒股软件!");
}
}
NBAObserver类(ConcreteObserver):
package com.jxs.observer;
/**
* Created by jiangxs on 2018/5/8.
*/
public class NBAObserver implements Observer {
@Override
public void update(Subject subject, Object info) {
System.out.println(this.getClass().getSimpleName()
+","+info+","
+"赶紧关闭NBA直播视频!");
}
}
客户端:
package com.jxs.observer;
/**
* Created by jiangxs on 2018/5/8.
*/
public class Client {
public static void main(String[] args) {
Boss boss = new Boss();
StockObserver stockObserver = new StockObserver();
NBAObserver nbaObserver = new NBAObserver();
boss.attach(stockObserver);
boss.attach(nbaObserver);
boss.notifyAllObserver("老板我回来了");
}
}
运行结果:
StockObserver,老板我回来了,赶紧关闭炒股软件!
NBAObserver,老板我回来了,赶紧关闭NBA直播视频!
Process finished with exit code 0
4.观察者模式的总结
(1)观察者模式的优点
观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。因此观察者模式很好地解耦了通知者与观察者,观察者不需要了解通知者内部是怎样实现的,方便于日后代码的修改,体现了依赖倒转的原则。
(2)观察者模式的缺点
①如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
②如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知
(3)观察者模式的适用场景
①关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
②事件多级触发场景。
③跨系统的消息交换场景,如消息队列、事件总线的处理机制。
5.使用观察者模式所遇到的问题
我们从上面的例子中可以看到,当老板回来的时候,调用了Boss对象的notifyAllObserver方法,这个方法中调用了Observer接口中的update方法。但是当StockObserver和NBAObserver中的方法不一样(例如玩股票的员工听说老板来了,它要关闭股票软件,而看NBA的员工听说老板来了,它要关闭视频直播软件),无法抽取成一个接口,在notifyAllObserver方法中该如何去完成通知功能呢?判断每一个对象属于哪个具体的观察者,然后分别调用它们的方法?很显然这样不仅麻烦,写出的代码也具有很强的耦合性,那么我们该怎么办呢?这个时候,我们就要使用一个巧妙的方法————委托。
6.在Java中使用委托
在《大话设计模式》当中,我发现当使用委托的时候,由于这本书用的是C#,所以里面使用delegate关键字就可以简单的去实现委托。于是我想在Java中有没有和C#一样的捷径去使用某个关键字或者其他简单的方式去实现委托呢?结果找了半天并没有发现解决委托的简单方式,于是,自己参考网上的例子,做了一个委托的封装去实现Java中的委托。
我们还是以上面的例子为例,只是当老板回来,或者前台MM通知员工老板回来后,员工们具体的动作不一样。也就是取消了公共接口Observer。
代码实现如下所示:
首先我们定义一个事件类Event类:
package com.jxs.observerWithDelegate;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by jiangxs on 2018/5/8.
* <p>
* 事件类
*/
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) {
int length = params.length;
this.paramTypes = new Class[length];
for (int i = 0;i<length;i++) {
paramTypes[i] = params[i].getClass();
}
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
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 setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
/**
* 根据对象的方法和参数类型利用反射来执行相关方法
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public void invoke() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = object.getClass().getMethod(this.getMethodName(), this.getParamTypes());
if (null == method) {
return;
}
method.invoke(this.getObject(), this.getParams());
}
}
在上面的代码中,我们将事件封装成Event并放入List集合events中,然后通知事件时,调用invoke方法;就和上面例子中将观察者放入集合当中,当通知观察者时,将观察者遍历,并调用update方法,让观察者完成更新动作的流程是一样的。
然后开始写EventHandler类,它是多个Event事件类的载体,同时它也提供一个为所有Event执行指定时间的功能。
package com.jxs.observerWithDelegate;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jiangxs on 2018/5/8.
* <p>
* EventHandler类,Event类的载体,提供一个执行Event的所有方法的类
*/
public class EventHandler {
// 使用List集合来装载事件
private List<Event> events;
// 在构造器中初始化List
public EventHandler() {
events = new ArrayList<>();
}
/**
* 添加某个对象要执行的事件和所需要的参数
* @param object
* @param methodname
* @param args
*/
public void addEvent(Object object, String methodname, Object... args) {
events.add(new Event(object, methodname, args));
}
/**
* 通知所有对象执行指定的事件
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public void notifyAllObserver() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
for (Event event : events) {
event.invoke();
}
}
}
前台和老板都属于通知者,这里将他们抽取出来作为一个抽象模板类Notifier
package com.jxs.observerWithDelegate;
/**
* Created by jiangxs on 2018/5/8.
* <p>
* 通知者类
* 前台的秘书和老板都相当于一个通知者,将两者共同点抽取出来成为一个抽象类
* 他们有功能的方法:
* ①可能有新的员工开始浪了,增加需要通知的员工
* ②老板来了需要被通知
*/
public abstract class Notifier {
private EventHandler eventHandler = new EventHandler();
public EventHandler getEventHandler() {
return eventHandler;
}
public void setEventHandler(EventHandler eventHandler) {
this.eventHandler = eventHandler;
}
/**
* 需要增加通知者
* @param object 要添加的观察者
* @param methodName 观察者中要委托的方法名
* @param args 观察者中要委托方法的参数(没有参数时可不写)
*/
public abstract void addListener(Object object, String methodName, Object... args);
/**
* 告诉所有人老板来了
*/
public abstract void notifyAllObserver();
}
Secretary类(具体通知类,前台MM):
package com.jxs.observerWithDelegate;
import java.lang.reflect.InvocationTargetException;
/**
* Created by jiangxs on 2018/5/8.
* <p>
* 通知者类Secretary类,Boss进来前通知所有人
*/
public class Secretary extends Notifier {
@Override
public void addListener(Object object, String methodName, Object... args) {
System.out.println("有新的员工委托前台放哨了");
// 使用工厂类获得EventHandler对象,然后调用该对象的方法
this.getEventHandler().addEvent(object,methodName,args);
}
@Override
public void notifyAllObserver() {
System.out.println("老板回来了,要准备进去了!");
try {
this.getEventHandler().notifyAllObserver();
} catch (NoSuchMethodException e) {
e.printStackTrace();
System.out.println(e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
System.out.println(e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
Boss类(具体通知类,老板):
package com.jxs.observerWithDelegate;
import java.lang.reflect.InvocationTargetException;
/**
* Created by jiangxs on 2018/5/8.
* <p>
* 通知者类Boss类,Boss进来视察也相当于充当了通知者
*/
public class Boss extends Notifier {
@Override
public void addListener(Object object, String methodName, Object... args) {
System.out.println("有新的员工要为老板的到来准备");
// 使用工厂类获得EventHandler对象,然后调用该对象的方法
this.getEventHandler().addEvent(object,methodName,args);
}
@Override
public void notifyAllObserver() {
System.out.println("老板走过来了!");
try {
this.getEventHandler().notifyAllObserver();
} catch (NoSuchMethodException e) {
e.printStackTrace();
System.out.println(e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println(e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
StockListener类(具体的观察者,炒股的员工):
package com.jxs.observerWithDelegate;
import java.util.Date;
/**
* Created by jiangxs on 2018/5/8.
*
* 在上班时间炒股的员工
*/
public class StockListener {
public void tradingStock() {
System.out.println("炒股员工正在炒股" + new Date());
}
public void stopTradeStock() {
System.out.println("老板来了,关闭炒股软件" + new Date());
}
}
NBAListener类(具体的观察者,看NBA的员工):
package com.jxs.observerWithDelegate;
import java.util.Date;
/**
* Created by jiangxs on 2018/5/8.
*
* 在上班时间看NBA的员工
*/
public class NBAListener {
public void watchingNBA() {
System.out.println("看球员工在看NBA" + " " + new Date());
}
public void stopWatchNBA() {
System.out.println("老板来了,关闭NBA直播"+ " " +new Date());
}
}
客户端:
package com.jxs.observerWithDelegate;
/**
* Created by jiangxs on 2018/5/8.
*
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 创建一个前台MM去放哨
Secretary secretary = new Secretary();
// 创建一个在工作时间炒股的员工
StockListener stockListener = new StockListener();
// 创建一个在工作时间看球的员工
NBAListener nbaListener = new NBAListener();
// 炒股的员工让前台帮他看着,老板来了记得提醒他
secretary.addListener(stockListener, "stopTradeStock");
// 看NBA的员工让前台帮他看着,老板来了记得提醒他
secretary.addListener(nbaListener, "stopWatchNBA");
// 看球员工和炒股员工开始愉快的玩耍
stockListener.tradingStock();
nbaListener.watchingNBA();
// 他们愉快的玩了一会以后
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 前台通知所有人老板回来了
secretary.notifyAllObserver();
}
}
运行结果:
有新的员工委托前台放哨了
有新的员工委托前台放哨了
炒股员工正在炒股Thu May 10 08:14:33 CST 2018
看球员工在看NBA Thu May 10 08:14:33 CST 2018
老板回来了,要准备进去了!
老板来了,关闭炒股软件Thu May 10 08:14:34 CST 2018
老板来了,关闭NBA直播 Thu May 10 08:14:34 CST 2018
Process finished with exit code 0
7.通过委托的例子得到的结论
从上面的例子当中我们可以看到使用委托的优点有哪些呢?
(1)由于Event和EventHandler这两个通用性很好的类存在,通知者完全不知道观察者的存在,完全解耦。
(2)老板来了后炒股员工关闭炒股软件,看NBA的员工关闭直播软件,实现了一次通知就执行了不同类的不同方法。
(3)扩展性很高,假设再增加一个打王者荣耀的员工,只需要在客户端中向通知者增加一个观察者即可,通知者无需修改。同时代码的重用性好。
注:以上代码均可在github上进行下载:https://github.com/xsongj/designPattern
参考:
《大话设计模式》
https://blog.csdn.net/yanshujun/article/details/6494447