Java设计模式-观察者模式(Observer Pattern)
目录
- 什么是观察者模式
- 观察者的2种实现方式
- JavaSE观察者模式的应用
- Struts2观察者模式的应用
一个对象的变化会通知关联对象
本文使用JDK1.8
一、什么是观察者模式
观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
我们日常用的微信公众号、消息推送、邮件订阅都是类似观察者模式,一个对象状态改变,依赖它的所有对象都会得到通知。
比如上面的UML图,模拟了微信订阅号通知的场景,当JavaAudit发生状态变化时,变调用notifyObservers函数通知所有的公众号订阅人(Observer)
观察者模式的主要角色:
Observable被观察者:可以与被观察者实体合并为一个接口或者抽象类
Observable实现类:定义被观察者自己的业务逻辑,定义哪些事件需要通知
Observer观察者:收到消息后执行特定动作,例如update方法
Observer实现类:定义自己的收到消息后的逻辑
观察者模式的其主要优点如下
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
主要的应用场景:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
二、观察者的2种实现方式
2.1 自定义实现
我们以此为例实现订阅号推送的功能
package org.observer.version1;
import java.util.ArrayList;
import java.util.List;
interface Weixin{
public void sendMessage(String msg);
public void postArticle(String msg);
}
interface Observable{
public void addObserver(Observer obs);
public void deleteObservable(Observer obs);
public void notifyObservers(Object msg);
}
class JavaAudit implements Weixin,Observable{
private List<Observer> observers = new ArrayList<>();
@Override
public void sendMessage(String msg) {
this.notifyObservers("您关注的公众号发消息了,消息内容为:" + msg);
}
@Override
public void postArticle(String msg) {
this.notifyObservers("您关注的公众号发文章了,文章内容为:" + msg);
}
@Override
public void addObserver(Observer obs) {
this.observers.add(obs);
}
@Override
public void deleteObservable(Observer obs) {
this.observers.remove(obs);
}
@Override
public void notifyObservers(Object msg) {
for(Observer obs:observers){
obs.update(msg);
}
}
}
interface Observer{
public void update(Object msg);
}
class ProgrammerA implements Observer{
private String name = "程序员A";
@Override
public void update(Object msg) {
System.out.println(this.name + "的微信通知:" + msg);
}
}
class ProgrammerB implements Observer{
private String name = "程序员B";
@Override
public void update(Object msg) {
System.out.println(this.name + "的微信通知:" + msg);
}
}
class ProgrammerC implements Observer{
private String name = "程序员C";
@Override
public void update(Object msg) {
System.out.println(this.name + "的微信通知:" + msg);
}
}
public class TestObserver {
public static void main(String[] args) {
ProgrammerA a = new ProgrammerA();
ProgrammerB b = new ProgrammerB();
ProgrammerC c = new ProgrammerC();
// 公众号
JavaAudit javaAudit = new JavaAudit();
// 公众号的关注者
javaAudit.addObserver(a);
javaAudit.addObserver(b);
javaAudit.addObserver(c);
// 公众号发消息
javaAudit.sendMessage("今晚8点抽奖");
javaAudit.sendMessage("如何学习代码审计");
}
}
// 运行结果
程序员A的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员B的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员C的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员A的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
程序员B的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
程序员C的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
2.2 使用JDK的类实现
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
- void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
这两个JDK自带的类完全可以替代我们自己定义的接口,我们来看下如何实现
package org.observer.version2;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
interface Weixin{
public void sendMessage(String msg);
public void postArticle(String msg);
}
class JavaAudit extends Observable implements Weixin {
private List<Observer> observers = new ArrayList<>();
@Override
public void sendMessage(String msg) {
this.notifyObservers("您关注的公众号发消息了,消息内容为:" + msg);
}
@Override
public void postArticle(String msg) {
this.notifyObservers("您关注的公众号发文章了,文章内容为:" + msg);
}
@Override
public void addObserver(Observer obs) {
this.observers.add(obs);
}
@Override
public void deleteObserver(Observer obs) {
this.observers.remove(obs);
}
@Override
public void notifyObservers(Object msg) {
for(Observer obs:observers){
obs.update(this,msg);
}
}
}
class ProgrammerA implements Observer {
private String name = "程序员A";
@Override
public void update(Observable o, Object arg) {
System.out.println(this.name + "的微信通知:" + arg.toString());
}
}
class ProgrammerB implements Observer {
private String name = "程序员B";
@Override
public void update(Observable o, Object arg) {
System.out.println(this.name + "的微信通知:" + arg.toString());
}
}
class ProgrammerC implements Observer {
private String name = "程序员C";
@Override
public void update(Observable o, Object arg) {
System.out.println(this.name + "的微信通知:" + arg.toString());
}
}
public class TestObserver {
public static void main(String[] args) {
ProgrammerA a = new ProgrammerA();
ProgrammerB b = new ProgrammerB();
ProgrammerC c = new ProgrammerC();
// 公众号
JavaAudit javaAudit = new JavaAudit();
// 公众号的关注者
javaAudit.addObserver(a);
javaAudit.addObserver(b);
javaAudit.addObserver(c);
// 公众号发消息
javaAudit.sendMessage("今晚8点抽奖");
javaAudit.sendMessage("如何学习代码审计");
}
}
// 运行结果
程序员A的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员B的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员C的微信通知:您关注的公众号发消息了,消息内容为:今晚8点抽奖
程序员A的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
程序员B的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
程序员C的微信通知:您关注的公众号发消息了,消息内容为:如何学习代码审计
只是替换了观察者模式的两个类,Observer的update方法把被观察者也传过来了,仅仅2点变化,其它的都类似,运行结果完全一样。
三、JavaSE观察者模式的应用
JDK中rt.jar的BCEL使用了观察者模式,先介绍下BCEL(Apache Byte Code Engineering Library),可以直接操作class文件,对class文件动态添加属性、方法
接下来在TestBCEL.class文件中动态添加一个方法
TestBCEL类的内容如下
package org.observer.version3;
public class TestBCEL {
public void test(){
System.out.println("test");
}
}
动态添加的执行类如下
package org.observer.version3;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.generic.*;
import java.io.IOException;
public class BCEL {
public static void main(String[] args) throws IOException {
JavaClass clazz = Repository.lookupClass(TestBCEL.class);
ClassGen classGen = new ClassGen(clazz);
// 添加监听者
classGen.addObserver(new MyObserver());
// 常量池信息
ConstantPoolGen cPoolGen = classGen.getConstantPool();
// 方法体,这里为空
InstructionList instructionDoPlan = new InstructionList();
instructionDoPlan.append(new RETURN());
MethodGen doPlanMethodGen = new MethodGen(1, Type.VOID, Type.NO_ARGS, null, "test1",
classGen.getClassName(), instructionDoPlan, cPoolGen);
classGen.addMethod(doPlanMethodGen.getMethod());
// 通知监听者
classGen.update();
JavaClass target = classGen.getJavaClass();
target.dump("TestBCEL1.class");
}
}
// 运行结果
org.observer.version3.TestBCEL的第0个方法:<init>
org.observer.version3.TestBCEL的第1个方法:test
org.observer.version3.TestBCEL的第2个方法:test1
打开生成的TestBCEL1.class文件,可以发现多了test1方法
读代码可以发现我们在BCEL类中,有两段代码
// 添加监听者
classGen.addObserver(new MyObserver());
// 通知监听者
classGen.update();
这就是BCEL观察者模式的应用,我们通过实现ClassObserver类,创造监听者,当使用BCEL动态修改class时告诉ClassObserver的实现类,MyObserver的代码如下
package org.observer.version3;
import com.sun.org.apache.bcel.internal.generic.ClassGen;
import com.sun.org.apache.bcel.internal.generic.ClassObserver;
import com.sun.org.apache.bcel.internal.classfile.Method;
public class MyObserver implements ClassObserver {
@Override
public void notify(ClassGen clazz) {
Method[] methods = clazz.getMethods();
for(int i=0;i<methods.length;i++){
Method m = methods[i];
System.out.println(clazz.getClassName() + "的第" + i + "个方法:" + m.getName());
}
}
}
GenClass类是被监听者,如下图实现了添加监听者、删除监听者以及通知的方法
四、Struts2观察者模式的应用
暂未发现,如果读者有发现,请联系我添加,谢谢