一、 用自己的理解阐述观察者模式的概念和过程
观察者模式,是一种通过定义被观察对象和观察对象间一对多的依赖关系,实现了 当被观察对象的状态改变时,所有依赖于它的观察者都会得到通知并被自动更新的模式。
观察者模式中非常重要的一点是观察者和被观察对象之间的互动关系不能体现成类之间的直接调用。直接调用会使得观察者对象和被观察对象之间变得紧密耦合起来。
观察者对象和被观察对象在功能模块上是相互独立的。通常观察者模式中会定义被观察的类Subject(其中会包含观察者队列、添加和撤销观察者对象以及广播通知观察者的方法等)、观察者父接口Observer(包含了未实现的更新方法等,观察者类通过继承Observer接口来实现)、主测试类等。Java的API中有两组接口类是用来写观察者模式用的,分别是java.util.Observable、java.util.Observer和java.util.EventListener、Java.util.EventObject。
二、案例链接
接下来将通过摄像头和门卫对商场进出人员监控的案例来分析观察者模式的实现过程和特点。
摄像头和门卫对商场进出人员监控:https://github.com/PapiLi/ObserverMode/tree/master/ObserverDemo/src/simple/observer
三、案例分析:
本案例中,被观察的对象是商店,发生更新和变化的状态是商店内的进出人员情况,观察者是门卫和摄像头,门卫主要负责实名记录进入商场的人的名字和登记离开,摄像头负责观察记录商场内的总人数。
(1)定义观察者接口Watcher
定义一个观察者父接口,其中只包含了观察者在接收到了被观察者广播的消息后的行动方法。
/** * 观察者接口 * */ public interface Watcher { /** * (接收到通知后,)观察者采取行动 * @param msg */ public void takeAction(Object[] msg); }
(2)定义被观察者接口Watchable
定义被观察者父类,其中定义了绑定在当前对象上的观察者集合、管理观察者集合、向观察者广播消息的方法。
import java.util.HashSet; import java.util.Set; /** * "被观察对象" 的父类 * */ public class Watchable { private Set<Watcher> watcherSet = new HashSet<Watcher>(); /** * 通知观察者 * @param params 通知的信息数组 */ protected void notifyWatchers(Object[] params){ for(Watcher watcher : watcherSet) { watcher.takeAction(params); //观察者采取行动 } } /** * 增加观察者 * @param watcher */ protected void addWatcher(Watcher watcher){ watcherSet.add(watcher); } /** * 移除观察者 * @param watcher */ protected void removeWatcher(Watcher watcher) { watcherSet.remove(watcher); } }
(3)继承被观察者接口实现商场Shop类
商场类继承Watchable类,并定义了COMEIN和GETOUT访客进出登记的动作。
import java.util.HashSet; import java.util.Set; /** * 店类,可被观察的 * */ public class Shop extends Watchable { static String IN = "in"; //进入 static String OUT = "out"; //出去 Set<String> personSet = new HashSet<String>(); int pcount = 0; //店内总人数 /** * 来访登记 * @param name */ public void comeIn(String name){ personSet.add(name); pcount = personSet.size(); Object[] parameters=new Object[]{IN, name, pcount}; notifyWatchers(parameters); //通知观察者 } /** * 出店登记 * @param name */ public void getOut(String name){ personSet.remove(name); pcount = personSet.size(); Object[] parameters=new Object[]{OUT, name, pcount}; notifyWatchers(parameters); //通知观察者 } }
(4)继承观察者接口实现Camera摄像头类
Camera类定义了takeAction方法,对于广播消息产生的动作是记录,print总人数。
/** * 摄像头类 * */ public class Camera implements Watcher { private int id; //摄像头编号 public Camera(int id){ this.id = id; } @Override public void takeAction(Object[] msg) { System.out.println("摄像头" + id + "号监测到现在店内总人数为" + msg[2] + "人"); } }
(5)继承观察者接口实现门卫Guard类
Guard类对访客进出消息做出print某人进/出商场的动作。
/** * 门卫(保安)类 * */ public class Guard implements Watcher { private String name; //门卫姓名 public Guard(){} public Guard(String name){ this.name = name; } @Override public void takeAction(Object[] msg) { if(msg[0].equals("in")){ System.out.println(this.name + "向管理处汇报:“" + msg[1] + "”已进入本店!"); } else if(msg[0].equals("out")){ System.out.println(this.name + "向管理处汇报:“" + msg[1] + "”已走出本店!"); } } }
各接口、类的继承和关联关系如下图所示:
下面为测试类和测试结果,可以观察到的是在为Shop绑定了Guard观察者实例时,门卫Guard实例会同步记录进出人员状况,而解绑后,则只有摄像头根据广播消息自动产生的反应。
public class Test { /** * @param args */ public static void main(String[] args) { Shop shop = new Shop(); //添加摄像头1号 Camera camera = new Camera(1); shop.addWatcher(camera); //添加门卫一号 Guard guard1 = new Guard("门卫A"); shop.addWatcher(guard1); //来访登记、出门登记 shop.comeIn("PAPI1"); shop.comeIn("PAPI2"); shop.getOut("PAPI1"); //移除门卫一号,派他去做别的事 shop.removeWatcher(guard1); //进入无人观察状态,随进随出 shop.comeIn("PAPILI"); shop.getOut("PAPI2"); shop.comeIn("阿宁"); //添加门卫二号 Guard guard2 = new Guard("门卫二号"); shop.addWatcher(guard2); //来访登记,出门登记 shop.comeIn("PAPI3"); shop.getOut("阿宁"); } }
观察者模式还有一个名称叫广播-监听模式,从此案例也可以反映出来,一个被观察对象可以有多个监听对象(观察者), Observable在发送广播通知的时候,无须指定具体的Observer,只需通过测试/代理类来决定是否要订阅被观察者的通知。被观察者至少需要有三个方法:添加监听者、移除监听者、通知Observer的方法;观察者至少要有一个方法:更新方法,更新当前的内容,作出相应的处理。这些特点使得观察者模式能在以下这种场景发挥很大的作用:1.对一个对象状态的更新需要其他对象同步更新或者一个对象的更新需要依赖另一个对象的更新。2.对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送。