设计模式之观察者模式
工作中我们经常会出现这样一种场景:在工作不忙的时候,老板出去办事了,然后很多员工就在工位上开始忙自己的事,比如看股票,看视频,看小说,吃零食。前台员工充当哨兵,只要老板一来,就马上通知员工们。
第一版,双向耦合代码实现:
//秘书充当前哨 public class Secretary { private List<StockObserver> observers = new ArrayList<StockObserver>(); private String action; public void attach(StockObserver observer){ observers.add(observer); } public void notifyObservers(){ for(StockObserver so : observers){ so.update(); } } public String getAction() { return action; } public void setAction(String action) { this.action = action; } }
public class StockObserver { private String name; private Secretary sub; public StockObserver(String name, Secretary sub){ this.name = name; this.sub = sub; } public void update(){ System.out.println(sub.getAction() + " " + name + " 关闭股票行情,继续工作!"); } }
public class Test { public static void main(String[] args) { //前台人员 Secretary xxx = new Secretary(); //看股票的同事 StockObserver yyy = new StockObserver("xiaoming", xxx); StockObserver zzz = new StockObserver("xiaohua", xxx); //前台记下两位同事 xxx.attach(zzz); xxx.attach(yyy); xxx.setAction("老板来了"); xxx.notifyObservers(); } }
我们发现“前台”类与“看股票者”是双向耦合的,如果观察者中还有想看NBA直播、看电影的,那“前台”类就得改动。这违反了开放-封闭原则,也违背了依赖倒转原则,我们应该让程序都依赖抽象,而不是互相依赖。我们发现,老板也好,前台也好,秘书也好,都是具体的通知者。这里观察者也不应该依赖具体的实现,而是一个抽象的通知者。
第二版,解耦实践:
//通知者接口 public interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers(); String getSubjectState(); void setSubjectState(String action); }
public class Boss implements Subject{ private List<Observer> observers = new ArrayList<Observer>(); private String action; @Override public void attach(Observer observer) { observers.add(observer); } @Override public void detach(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for(Observer o : observers){ o.update(); } } @Override public String getSubjectState() { return this.getAction(); } public String getAction() { return action; } public void setAction(String action) { this.action = action; } @Override public void setSubjectState(String action) { this.setAction(action); } }
前台类与老板类类似,略。
public abstract class Observer { protected String name; protected Subject sub; public Observer(String name, Subject sub){ this.name = name; this.sub = sub; } public abstract void update(); }
public class StockObserver1 extends Observer{ public StockObserver1(String name, Subject sub){ super(name, sub); } public void update(){ System.out.println(sub.getSubjectState() + " " + name + " 关闭股票行情,继续工作!"); } }
public class NBAObserver extends Observer{ public NBAObserver(String name, Subject sub){ super(name, sub); } public void update(){ System.out.println(sub.getSubjectState() + " " + name + " 关闭NBA直播,继续工作!"); } }
public class Test2 { public static void main(String[] args) { //前台人员 Subject boss = new Boss(); //看股票的同事 Observer yyy = new StockObserver1("xiaoming", boss); Observer zzz = new NBAObserver("xiaohua", boss); //前台记下两位同事 boss.attach(zzz); boss.attach(yyy); boss.detach(yyy); boss.setSubjectState("你们的boss我回来了"); boss.notifyObservers(); } }
这就是改进版的观察者模式。
观察者模式:也叫做发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有的观察者,使他们能够自动地更新自己。
观察者模式特点:
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是要维护相关对象间的一致性。这个给维护、扩展、重用都带来了不便。而观察者模式解决了这个不便。那什么时候该用此模式呢?当一个对象的改变需要同时改变其他对象的时候。总的来讲,观察者模式所做的工作就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖具体,从而使得各自的变化都不会影响到另一边的变化。该实现方法也有不足之处,尽管已经使用了依赖倒转原则,但“抽象通知者”还是依赖“抽象观察者”,也就是说万一没了抽象观察者这样的接口,这通知功能就完不成了。另外就是每个具体的观察者,它不一定是“更新”的方法要调用,而是其他不同名的方法。
第三版,事件委托实现
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){ this.paramTypes = new Class[params.length]; for(int i = 0; i < params.length; i++){ this.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; } //执行该对象的该方法 public void invoke() throws Exception{ String name = this.getMethodName(); Method method = object.getClass().getMethod(this.getMethodName(), this.getParamTypes()); if(null == method){ return; } method.invoke(this.getObject(), this.getParams()); } }
public class EventHandler { private List<Event> objects; public EventHandler(){ objects = new ArrayList<Event>(); } //添加某个对象要执行的事件及需要的参数 public void addEvent(Object object, String methodName, Object...args){ objects.add(new Event(object, methodName, args)); } //通知所有的对象执行指定的事件 public void notifyX() throws Exception{ for(Event e : objects){ e.invoke(); } } }
public abstract class Notifier { private EventHandler eventHandler = new EventHandler(); //增加需要帮忙放哨的学生 public abstract void addListener(Object object, String methodName, Object...args); public abstract void notifyX(); public EventHandler getEventHandler() { return eventHandler; } public void setEventHandler(EventHandler eventHandler) { this.eventHandler = eventHandler; } }
public class GoodNotifier extends Notifier{ @Override public void addListener(Object object, String methodName, Object... args) { System.out.println("有新的同学委托尽职尽责的放哨人"); this.getEventHandler().addEvent(object, methodName, args); } @Override public void notifyX() { System.out.println("尽职尽责的放哨人告诉所有需要帮忙的同学:老师来了"); try { this.getEventHandler().notifyX(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
public class PlayingGameListener { public PlayingGameListener(){ System.out.println("我正在玩游戏 开始时间:" + new Date()); } public void stopPlayingGame(Date date){ System.out.println("老师来了,快回到座位上,结束时间:" + date); } }
public class WatchingTVListener { public WatchingTVListener(){ System.out.println("我正在看电视开始时间:" + new Date()); } public void stopWatchingTV(Date date){ System.out.println("老师来了,快关闭电视,结束时间:" + date); } }
public class Test { public static void main(String[] args){ Notifier goodNotifier = new GoodNotifier(); PlayingGameListener playingGameListener = new PlayingGameListener(); WatchingTVListener watchingTVListener = new WatchingTVListener(); goodNotifier.addListener(playingGameListener, "stopPlayingGame", new Date()); goodNotifier.addListener(watchingTVListener, "stopWatchingTV", new Date()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } goodNotifier.notifyX(); } }
现在可以解释一下委托是什么了。委托就是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法可以像其他任何方法一样,具有参数和返回值,委托可以看做是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。new EventHandler()其实就是创建了一个委托实例,一个委托可以搭载多个方法,所有方法被依次唤起,它可以使委托对象所搭载的方法并不需要属于同一个类。但委托也是有前提的,那就是委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。