观察者模式
一、定义
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。观察者模式的主要角色如下:
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
二、观察者模式的案例
观察者模式在现实场景非常多,例如闹钟设置,APP角标通知,邮件通知,广播通知,在软件设计中当系统一方行为依赖另一方行为的变化时,就可以使用观察者模式松耦合联动双方,使一方的变化可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应,观察者模式适用如下几种场景。
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 其他一个或多个对象的变化依赖于另一个对象的变化。
- 实现类似广播机制的功能,无需知道具体的接收者,只需分发广播,系统中感兴趣的对象会自动接收这个广播。
- 多层级嵌套使用,形成一个链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
1、标准写法
//抽象观察者 public interface IObserver<E> { void update(E event); }
//具体观察者 public class ConcreteObserver<E> implements IObserver<E> { public void update(E event) { System.out.println("receive event: " + event); } }
//抽象主题者 public interface ISubject<E> { boolean attach(IObserver<E> observer); boolean detach(IObserver<E> observer); void notify(E event); }
//具体主题者 public class ConcreteSubject<E> implements ISubject<E> { private List<IObserver<E>> observers = new ArrayList<IObserver<E>>(); public boolean attach(IObserver<E> observer) { return !this.observers.contains(observer) && this.observers.add(observer); } public boolean detach(IObserver<E> observer) { return this.observers.remove(observer); } public void notify(E event) { for (IObserver<E> observer : this.observers) { observer.update(event); } } }
public class Test { public static void main(String[] args) { // 被观察者 ISubject<String> observable = new ConcreteSubject<String>(); // 观察者 IObserver<String> observer = new ConcreteObserver<String>(); // 注册 observable.attach(observer); // 通知 observable.notify("hello"); } }
2.采用java的JDK实现观察者模式,就拿博客园的消息提醒功能演示下
//采用JDK现有的轮子Observable public class Bloggarden extends Observable { private String name = "博客园"; private static final Bloggarden bloggarden = new Bloggarden(); private Bloggarden() {} public static Bloggarden getInstance(){ return bloggarden; } public String getName() { return name; } public void publishQuestion(Question question){ System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题。"); setChanged(); notifyObservers(question); } }
public class Question { private String userName; private String content; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
//采用JDK现有轮子Observer public class Teacher implements Observer { private String name; public Teacher(String name) { this.name = name; } @Override public void update(Observable o, Object arg) { Bloggarden bloggarden = (Bloggarden)o; Question question = (Question)arg; System.out.println("======================"); System.out.println(name + "你好!\n" + "您收到了一个来自" + bloggarden.getName() + "的提问,希望您解答。问题内容如下:\n" + question.getContent() + "\n" + "提问者:" + question.getUserName()); } }
public class Test { public static void main(String[] args) { Bloggarden bloggarden = Bloggarden.getInstance(); Teacher zs = new Teacher("张三"); Teacher ls = new Teacher("李四"); bloggarden.addObserver(zs); bloggarden.addObserver(ls); //用户行为 Question question = new Question(); question.setUserName("张三"); question.setContent("观察者模式的源码应用场景有哪些"); bloggarden.publishQuestion(question); } }
3.下面再举一个我们在实际场景用的比较多的guava场景,他是谷歌开源的一个包,他是一个非常轻量级的观察者落地,使用前先导包
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
//定义观察者,Guava是通过注解实现的 public class GuavaEvent { @Subscribe public void observer(String str){ System.out.println("执行observer方法,传参为:" + str); } }
客户端调用就十分简单了,只要是加了注解的Event回调都会收到消息,我这里面写的是字符串,如果想传对象啥的也行
public class Test { public static void main(String[] args) { //Guava的API,消息总线 EventBus eventBus = new EventBus(); GuavaEvent guavaEvent = new GuavaEvent(); //将guavaEvent注册到消息总线中去 eventBus.register(guavaEvent); //发送消息 eventBus.post("来自总线发送的消息"); } }
4、根据观察者定义造轮子,就以鼠标为例,这里面被观察者就是鼠标,观察者就是回调
//标准事件源格式的定义 public class Event { //事件源,动作是由谁发出的 private Object source; //事件触发,要通知谁(观察者) private Object target; //观察者给的回应 private Method callback; //事件的名称 private String trigger; //事件的触发时间 private long time; public Event(Object target, Method callback) { this.target = target; this.callback = callback; } public Object getSource() { return source; } public Event setSource(Object source) { this.source = source; return this; } public String getTrigger() { return trigger; } public Event setTrigger(String trigger) { this.trigger = trigger; return this; } public long getTime() { return time; } public Event setTime(long time) { this.time = time; return this; } public Method getCallback() { return callback; } public Object getTarget() { return target; } @Override public String toString() { return "Event{" + "source=" + source + ", target=" + target + ", callback=" + callback + ", trigger='" + trigger + '\'' + ", time=" + time + '}'; } }
//观察者抽象:事件监听器 public class EventListener { protected Map<String,Event> events=new HashMap<String, Event>(); public void addLisenter(String eventType, Object target, Method callback){ events.put(eventType,new Event(target,callback)); } public void addLisenter(String eventType, Object target){ try { this.addLisenter(eventType,target,target.getClass().getMethod("on"+toUpperFirstCase(eventType),Event.class)); }catch (NoSuchMethodException e){ e.printStackTrace(); } } private String toUpperFirstCase(String eventType) { char [] chars = eventType.toCharArray(); chars[0] -= 32; return String.valueOf(chars); } private void trigger(Event event){ event.setSource(this); event.setTime(System.currentTimeMillis()); try { if (event.getCallback() != null) { //用反射调用回调函数 event.getCallback().invoke(event.getTarget(), event); } }catch (Exception e){ e.printStackTrace(); } } protected void trigger(String string){ if(!this.events.containsKey(string)){return;} trigger(this.events.get(string).setTrigger(string)); } }
public interface MouseEventType { //单击 String ON_CLICK = "click"; //双击 String ON_DOUBLE_CLICK = "doubleClick"; //弹起 String ON_UP = "up"; //按下 String ON_DOWN = "down"; //移动 String ON_MOVE = "move"; //滚动 String ON_WHEEL = "wheel"; //悬停 String ON_OVER = "over"; //失焦 String ON_BLUR = "blur"; //获焦 String ON_FOCUS = "focus"; }
//具体的被观察者:鼠标 public class Mouse extends EventListener { public void click(){ System.out.println("调用单击方法"); this.trigger(MouseEventType.ON_CLICK); } public void doubleClick(){ System.out.println("调用双击方法"); this.trigger(MouseEventType.ON_DOUBLE_CLICK); } public void up(){ System.out.println("调用弹起方法"); this.trigger(MouseEventType.ON_UP); } public void down(){ System.out.println("调用按下方法"); this.trigger(MouseEventType.ON_DOWN); } public void move(){ System.out.println("调用移动方法"); this.trigger(MouseEventType.ON_MOVE); } public void wheel(){ System.out.println("调用滚动方法"); this.trigger(MouseEventType.ON_WHEEL); } public void over(){ System.out.println("调用悬停方法"); this.trigger(MouseEventType.ON_OVER); } public void blur(){ System.out.println("调用获焦方法"); this.trigger(MouseEventType.ON_BLUR); } public void focus(){ System.out.println("调用失焦方法"); this.trigger(MouseEventType.ON_FOCUS); } }
//观察者 public class MouseEventCallback { public void onClick(Event e){ System.out.println("触发鼠标单击事件"); } }
public class Test { public static void main(String[] args) { MouseEventCallback callback = new MouseEventCallback(); Mouse mouse = new Mouse(); mouse.addLisenter(MouseEventType.ON_CLICK,callback); mouse.addLisenter(MouseEventType.ON_MOVE,callback); mouse.click(); mouse.move(); } }
三、观察者模式在源码中的体现
在源码中一般以Listener结尾的都是观察者,在spring中的ContextLoaderListener 实现了ServletContextListener 接口,ServletContextListener 接口又继承了EventListener,在JDK中EventListener有非常广的应用
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
public interface ServletContextListener extends EventListener { void contextInitialized(ServletContextEvent var1); void contextDestroyed(ServletContextEvent var1); }
四、总结
优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
git源码:https://gitee.com/TongHuaShuShuoWoDeJieJu/design_pattern.git
这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦