观察者模式又称为发布-订阅(Publish/Subscribe)模式,是23种设计模式之一。DP中是这么定义观察者模式的:java
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知全部的观察者对象,使它们可以自动更新本身。设计模式
举个生活中的例子,例如在某班级里,几个同窗都在某个网站上订阅了一本连载的漫画。当漫画更新时,就会通知这几位同窗,同窗收到通知后就能够去下载漫画的最新篇章。这就是一个典型的观察者模式,在这里同窗都是观察者,而漫画则是他们共同监听的一个主题,而漫画更新时也就是主题对象发生变化时,就会通知全部订阅该漫画的同窗,因此漫画也就是通知者。同窗们收到通知后就能够去下载漫画的新篇章了,这就是主题对象会通知观察者对象让他们进行更新。数组
咱们使用简单的代码,来尝试实现一下这个场景:
漫画类,也就是主题:bash
package org.zero01.test; import java.util.Vector; // 漫画 public class Cartoon { // 学生列表 private Vector<Student> studentList = new Vector(); // 漫画状态 private String action; public String getAction() { return action; } public void setAction(String action) { this.action = action; } // 添加订阅该漫画的学生 public void attach(Student student) { studentList.add(student); } // 漫画更新时通知学生 public void notifyOfStu() { for (Student student : studentList) { student.update(); } } }
学生类,也就是观察者:ide
package org.zero01.test; public class Student { private String name; private Cartoon cartoon; public Student(String name, Cartoon cartoon) { this.name = name; this.cartoon = cartoon; } // 获得通知时,就去下载新漫画 public void update() { System.out.println(cartoon.getAction() + ", " + name + "请下载漫画的新篇章!"); } }
客户端:网站
package org.zero01.test; public class Client { public static void main(String[] args) { // 订阅的漫画 Cartoon cartoon=new Cartoon(); // 订阅该漫画的学生 Student student1=new Student("小明", cartoon); Student student2=new Student("小红", cartoon); // 添加订阅的学生 cartoon.attach(student1); cartoon.attach(student2); // 订阅的漫画更新了 cartoon.setAction("您订阅的XXX漫画更新啦"); // 通知订阅的学生 cartoon.notifyOfStu(); } }
运行结果:this
您订阅的XXX漫画更新啦, 小明请下载漫画的新篇章! 您订阅的XXX漫画更新啦, 小红请下载漫画的新篇章!
以上的代码实现了一个简单的观察者模式,当漫画这个主题对象的状态发生改变时,就会通知全部的订阅者。设计
咱们编写的代码虽然能够实现以上所说到的场景,可是代码的耦合性很高,不能彻底符合观察模式的设计理念。例如,我要增长一个订阅的是小说类型的学生,那么就得去修改 “漫画” 通知者的代码了。若是我还要增长一个 “小说” 通知者,让 小说” 通知者也能通知全部学生的话,也须要去修改学生类的代码,这就不符合开-闭原则了,并且对象之间互相依赖也违背了依赖倒转原则,以及以上的代码中没有编写取消订阅的方法也就是减小观察者的方法。code
既然知道代码有哪些问题了,那么咱们就来把这些代码重构一下:orm
代码结构图:
1.首先抽象两个类:
package org.zero01.test; // 通知者/主题接口 public interface Subject { public void attach(Observer observer); public void detach(Observer observer); public void notifyOfStu(); public void setAction(String action); public String getAction(); } package org.zero01.test; // 抽象观察者 public interface Observer { public abstract void update(); }
2.而后才是具体的实现类:
漫画类:
package org.zero01.test; import java.util.Vector; // 漫画主题 public class Cartoon implements Subject{ // 学生列表 private Vector<Observer> observerList = new Vector(); // 主题动做 private String action; public String getAction() { return action; } public void setAction(String action) { this.action = action; } // 添加订阅该漫画的学生 public void attach(Observer observer) { observerList.add(observer); } // 漫画更新时通知学生 public void notifyOfStu() { for (Observer observer : observerList) { observer.update(); } } public void detach(Observer observer) { observerList.remove(observer); } }
小说类:
package org.zero01.test; import java.util.Vector; // 小説主題 public class Story implements Subject{ // 学生列表 private Vector<Observer> observerList = new Vector(); // 主题动做 private String action; public void attach(Observer observer) { observerList.add(observer); } public void detach(Observer observer) { observerList.remove(observer); } public void notifyOfStu() { for (Observer observer : observerList) { observer.update(); } } public void setAction(String action) { this.action=action; } public String getAction() { return action; } }
订阅漫画的学生:
package org.zero01.test; public class StuOfCartoon extends Observer{ private String name; private Subject subject; public StuOfCartoon(String name, Subject subject) { this.name=name; this.subject=subject; } // 获得通知时,就去下载新漫画 public void update() { System.out.println(subject.getAction() + ", " + name + "请下载漫画的新篇章!"); } }
订阅小说的学生:
package org.zero01.test; public class StuOfStory extends Observer{ private String name; private Subject subject; public StuOfStory(String name, Subject subject) { this.name=name; this.subject=subject; } // 获得通知时,就去下载小说的新篇章 public void update() { System.out.println(subject.getAction() + ", " + name + "请下载小说的新篇章!"); } }
客户端代码:
package org.zero01.test; public class Client { public static void main(String[] args) { // 漫画 Cartoon cartoon = new Cartoon(); // 订阅漫画的学生 StuOfCartoon student1 = new StuOfCartoon("小明", cartoon); StuOfCartoon student2 = new StuOfCartoon("小红", cartoon); // 订阅小说的学生 StuOfStory ofStory=new StuOfStory("小刚", cartoon); // 添加订阅的学生 cartoon.attach(student1); cartoon.attach(student2); cartoon.attach(ofStory); // 取消订阅,减小订阅的学生 cartoon.detach(student2); // 订阅的主题更新了 cartoon.setAction("您订阅的XXX更新啦"); // 通知订阅的学生 cartoon.notifyOfStu(); } }
运行结果:
您订阅的XXX更新啦, 小明请下载漫画的新篇章! 您订阅的XXX更新啦, 小刚请下载小说的新篇章!
从客户端的代码能够看到,抽象了两个类以后,即使是只有一个 ”漫画“ 通知者也可以通知订阅不一样类型主题的观察者,而不须要去修改任何的代码。一样的,我把 ”漫画“ 通知者换成 “小说” 通知者也丝绝不会受到影响:
客户端代码:
package org.zero01.test; public class Client { public static void main(String[] args) { // 小说 Story story = new Story(); // 订阅漫画的学生 StuOfCartoon student1 = new StuOfCartoon("小明", story); StuOfCartoon student2 = new StuOfCartoon("小红", story); // 订阅小说的学生 StuOfStory ofStory=new StuOfStory("小刚", story); // 添加订阅的学生 story.attach(student1); story.attach(student2); story.attach(ofStory); // 取消订阅,减小订阅的学生 story.detach(student2); // 订阅的主题更新了 story.setAction("您订阅的XXX更新啦"); // 通知订阅的学生 story.notifyOfStu(); } }
运行结果:
您订阅的XXX更新啦, 小明请下载漫画的新篇章! 您订阅的XXX更新啦, 小刚请下载小说的新篇章!
这样的设计就知足了依赖倒转原则以及开-闭原则,算得上是一个完整的观察者模式设计的代码了。
监听与通知示意图:
咱们再来看看观察者模式(Observe)的结构图:
咱们来使用代码实现这个结构:
Subject类。该类一般被称为主题或抽象通知者,通常使用一个抽象类或者接口进行声明。它把全部对观察者对象的引用保存在一个集合里,每一个主题均可以有任何数量的观察者。抽象主题提供一个接口,能够增长和删除观察者对象:
import java.util.List; import java.util.Vector; public abstract class Subject { // 观察者列表 private List<Observer> observers = new Vector<Observer>(); // 增长观察者 public void attach(Observer observer) { observers.add(observer); } // 移除观察者 public void detach(Observer observer) { observers.remove(observer); } // 通知观察者 public void notifyOfObserver() { for (Observer observer : observers) { observer.update(); } } }
Observer类,抽象观察者,为为全部的具体观察者定义一个接口,在获得主题的通知时更新本身。这个接口叫更新接口。抽象观察者通常用一个抽象类或者一个接口实现。更新接口一般包含一个update方法,这个方法叫更新方法:
public abstract class Observer { public abstract void update(); }
ConcreteSubject类,叫作具体的主题或具体的通知者,该类将有关状态存入具体的观察者对象。在具体主题的内部状态改变时,给全部登记过的观察者发出通知。具体主题角色一般用一个具体的子类来进行实现:
public class ConcreteSubject extends Subject{ // 具体主题的状态 public String getSubjectState() { return subjectState; } public void setSubjectState(String subjectState) { this.subjectState = subjectState; } private String subjectState; }
ConcreteObserver类,具体的观察者类,实现抽象观察者角色所要求的更新接口,以便使自己的状态与主题的状态相协调。具体观察者角色能够保存一个指向具体主题对象的引用。具体观察者角色一般用一个具体的子类进行实现:
public class ConcreteObserver extends Observer { // 观察者名称 private String name; // 观察者状态 private String observerState; private ConcreteSubject concreteSubject; public ConcreteObserver(String name, ConcreteSubject concreteSubject) { this.name = name; this.concreteSubject = concreteSubject; } public ConcreteSubject getConcreteSubject() { return concreteSubject; } public void setConcreteSubject(ConcreteSubject concreteSubject) { this.concreteSubject = concreteSubject; } // 更新 public void update() { observerState = concreteSubject.getSubjectState(); System.out.println("观察者" + name + "的新状态是" + observerState); } }
客户端代码:
public class Client { public static void main(String[] args){ ConcreteSubject concreteSubject=new ConcreteSubject(); concreteSubject.attach(new ConcreteObserver("A",concreteSubject)); concreteSubject.attach(new ConcreteObserver("B",concreteSubject)); concreteSubject.attach(new ConcreteObserver("C",concreteSubject)); concreteSubject.setSubjectState("test state"); concreteSubject.notifyOfObserver(); } }
运行结果:
观察者A的新状态是test state 观察者B的新状态是test state 观察者C的新状态是test state
观察者模式特色:
将一个系统分割成一系列互相协做的类有一个很很差的反作用,那就是须要维护相关对象之间的一致性。咱们不但愿为了维持一致性而使各种紧密耦合,这样会给维护、扩展和复用都带来不便。而观察者模式的关键对象是主题 Subject 和观察者Observer ,一个 Subject 能够有任意数目的依赖它的 Observer ,一旦Subject的状态发生了改变,全部的Observer 均可以获得通知。Subject发出通知时并不须要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不须要知道。而任何一个具体观察者不知道也不须要知道其余观察者的存在,这样下降了子类之间的耦合。
何时考虑使用观察者模式?
1.当一个对象的改变须要同时改变其余对象的时候,并且它不知道具体有多少个对象有待改变时,应该考虑使用观察者模式
2.当一个抽象模型有两个方面,其中一方面依赖于另外一方面,这时用观察者模式能够将这二者封装在独立的对象中使它们各自独立地改变和复用。
观察者模式所作的事情其实就是解耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另外一边的变化。
观察者模式的不足:
咱们没办法让每一个控件都是实现一个 “Observer” 接口,由于这些控件都早已被它们的制造商封装好了。并且咱们上面的例子,尽管已经用了依赖倒转原则,可是 “抽象通知者” 仍是依赖 ”抽象观察者“ ,也就是说,万一没有了 ”抽象观察者“ 这样的接口,那么通知功能就没法完成了。既然 ”通知者“ 和 ”观察者“ 之间根本就互相不知道,那么咱们就换另外一种方式,让客户端来决定通知谁,这就是接下来要提到的事件委托模式。
事件委托模式在Java的Swing图形化中常用,可是在Java语言中没有对其作必定的封装,所以实现起来没那么容易,不过反射机制学得还不错的话,其实很好理解实现原理。相比之下C#就容易了不少,C#里有一个delegate关键字,只须要声明一个委托器就能够了。在Java中咱们须要本身经过反射机制去实现,正好把上面演示的例子使用事件委托模式进行重构,一会再说明什么是事件委托:
代码结构图:
1.去掉观察者Observer接口,把两个具体的观察者类的代码修改成以下内容:
package org.zero01.delegate; import java.util.Date; // 订阅漫画的同窗 public class StuOfCartoon { private String name; public StuOfCartoon(String name) { this.name = name; } // 获得通知时,就去下载新漫画 public void downloadNewCartoon(Date date) { System.out.println(date.toLocaleString() + " 您订阅的XXX更新啦, " + name + "请下载漫画的新篇章!"); } } package org.zero01.delegate; import java.util.Date; // 订阅小说的同窗 public class StuOfStory { private String name; public StuOfStory(String name) { this.name = name; } // 获得通知时,就去下载小说的新篇章 public void downloadNewStory(Date date) { System.out.println(date.toLocaleString() + " 您订阅的XXX更新啦, " + name + "请下载小说的新篇章!"); } }
2.定义一个事件类,该类经过反射机制完成对观察者对象方法的调用:
package org.zero01.delegate; 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... params) { this.object = object; this.methodName = methodName; this.params = params; 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 invoke() throws Exception { Method method = object.getClass().getMethod(this.getMethodName(), this.getParamTypes()); if (method == null) { return; } method.invoke(this.getObject(), this.getParams()); } // 如下都是属性的setter和getter,就省略了 }
3.事件处理类,该类将事件源信息收集给事件类:
package org.zero01.delegate; import java.util.ArrayList; import java.util.List; /** * 事件处理类,收集事件信息交给事件类执行 */ public class EventHandler { private List<Event> objects; public EventHandler() { objects = new ArrayList<Event>(); } // 添加某个观察者对象要执行的方法,以及方法所须要的参数 public void addEvent(Object object, String methodName, Object... params) { objects.add(new Event(object, methodName, params)); } // 通知全部的观察者对象执行指定的方法 public void notifyOfObserver() throws Exception { for (Event event : objects) { event.invoke(); } } }
4.通知者接口:
package org.zero01.delegate; // 通知者、主题接口 public interface Subject { // 增长观察者,也就是订阅的学生 public void addListener(Object object, String methodName, Object... params); // 通知学生订阅的内容更新了 public void notifyOfObserver(); }
5.具体的通知者:
package org.zero01.delegate; public class Cartoon implements Subject { private EventHandler eventHandler; public Cartoon(EventHandler eventHandler) { this.eventHandler = eventHandler; } // 添加事件 public void addListener(Object object, String methodName, Object... params) { eventHandler.addEvent(object, methodName, params); } // 转发到事件处理类上 public void notifyOfObserver() { try { eventHandler.notifyOfObserver(); } catch (Exception e) { e.printStackTrace(); } } }
Story类的代码也是同样的,忽略。
6.客户端代码:
package org.zero01.delegate; import java.util.Date; public class Client { public static void main(String[] args) { // 通知者 Subject cartoon = new Cartoon(new EventHandler()); // 订阅漫画的同窗 StuOfCartoon stuOfCartoon = new StuOfCartoon("小明"); // 订阅小说的同窗 StuOfStory stuOfStory = new StuOfStory("小红"); // 添加观察者,或者说添加订阅学生,把两个不一样的类以及不一样的方法委托给事件处理类 cartoon.addListener(stuOfCartoon, "downloadNewCartoon", new Date()); cartoon.addListener(stuOfStory, "downloadNewStory", new Date()); // 发出通知 cartoon.notifyOfObserver(); } }
以上客户端的代码能够看到,为了方便演示,咱们是经过字符串来传递须要执行的方法的名称。还有另外一种方式就是能够经过接口去定义方法的名称,就像Swing中添加点击事件同样,须要实现ActionListener接口里定义的actionPerformed方法,这样咱们就只须要传递观察者对象便可,而后反射机制就扫这个对象是否有实现接口中定义的方法就能够了。不过若是不是像点击事件那种固定不变的方法的话,仍是使用字符串来传递须要执行的方法的名称会好一些,这样便于修改。
运行结果:
2018-1-29 21:10:30 您订阅的XXX更新啦, 小明请下载漫画的新篇章! 2018-1-29 21:10:30 您订阅的XXX更新啦, 小红请下载小说的新篇章!
事件委托说明:
如今就能够来解释一下,事件委托是什么了。这就比如我是班长你是班主任,你让我通知某几个学生去办公室,而后我就去通知那几个学生办公室,这就是一个委托,你委托的事情是让我去通知你指定的那几个学生。而我就是通知者,与观察者模式不一样的是,我是由于有你的委托才能去通知学生,而观察者模式是当主题状态发生变化时通知观察者。上面的客户端代码里,咱们将订阅了相关内容的学生,委托给了通知者,因此通知者就能够对这些学生发出通知,但实际调用观察者方法的是Event类,不是通知者了。
并且一个委托能够搭载多个方法,这些方法能够是不一样类的方法,当发送通知时全部的方法会被依次调用。这样咱们就不须要在通知者上用一个集合存储观察者了,增长、减小观察者的方法也不须要编写了,而是转到客户端来让给委托搭载多个方法,这就解决了原本与抽象观察者耦合的问题。也就是说观察者模式是由抽象的观察者来决定调用哪一个方法,而事件委托模式是由客户端决定调用哪一个方法,这样通知者就不须要依赖抽象观察者了。