观察者模式分析
观察者模式又叫做发布-订阅(Publish/Subscribe)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
特点:
观察者模式的通知者可以有任意数目的依赖它的Observer,一旦通知者的状态改变,所有的Observer都可以得到通知。通知者发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。
总得来讲,观察者模式所做的工作其实就是在接触耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
那么在观察者模式时,如果观察者之间非常相似,那么可以用抽象类来共用一些代码,但是现实编程中,具体的观察者完全有可能是风马牛不相及的类,但它们都需要根据通知者的通知来做出Update()操作,所以让它们都实现下面一个接口就可以实现这个想法了。
interface Observer {
void update();
}
下面这个观察者模式,我会一步步以例子进行分析。
下面的观察者以工作中开小差玩游戏的同事为原型,通知者以秘书为原型,一旦老板出现,秘书立即通知玩游戏、睡觉的同事,做好准备。
双向耦合代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
package observer1;
import java.util.ArrayList; import java.util.List;
* Created by benjamin on 1/4/16. * 秘书类 */ public class Secretary { private List<PlayGameObserver> observers = new ArrayList<PlayGameObserver>(); private String noticeMessage;
public Secretary(){}
* 增加观察者 * @param observer */ public void addObserver(PlayGameObserver observer) { observers.add(observer); }
* 删除观察者 * @param observer */ public void removeObserver(PlayGameObserver observer) { observers.remove(observer); }
* 通知所有观察者 */ public void notifyObserver() { for (PlayGameObserver o : observers) { o.update(); } }
public String getNoticeMessage() { return noticeMessage; }
public void setNoticeMessage(String noticeMessage) { this.noticeMessage = noticeMessage; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
package observer1;
* Created by benjamin on 1/4/16. * 玩游戏的观察者 */ public class PlayGameObserver {
private String name; private Secretary sub;
public PlayGameObserver(String name, Secretary sub) { this.name = name; this.sub = sub; }
public void update() { System.out.println(sub.getNoticeMessage() + this.name + "关闭游戏,继续工作!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
package observer1;
* Created by benjamin on 1/4/16. */ public class ObserverTest {
public static void main(String[] args) { Secretary secretary = new Secretary(); PlayGameObserver o1 = new PlayGameObserver("王菲", secretary); secretary.addObserver(o1);
secretary.setNoticeMessage("老板回来了!"); secretary.notifyObserver(); } }
|
结果输出为:
老板回来了!王菲关闭游戏,继续工作!
上面的类虽然能实现功能,但是仔细一看,他们之间的耦合太大了。秘书类需要增加观察者,而观察者们需要秘书类。如果观察者不止一个人,并且干得事情不一样的时候(比如睡觉、吃东西什么的),就无法让秘书增加对应的观察者,并一起通知。我们来改改代码。
解耦
抽象观察者类(Observer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
package observer1;
* Created by benjamin on 1/4/16. * 观察者 */ public abstract class Observer {
protected String name; protected Notification notice;
public Observer(String name, Notification notice) { this.name = name; this.notice = notice; }
public abstract void update(); }
|
抽象通知者类(Notification)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
package observer1;
* Created by benjamin on 1/4/16. */ public abstract class Notification {
protected String noticeMessage;
public abstract void addObserver(Observer observer); public abstract void removeObserver(Observer observer); public abstract void notifyObserver();
public String getNoticeMessage() { return noticeMessage; }
public void setNoticeMessage(String noticeMessage) { this.noticeMessage = noticeMessage; } }
|
观察者们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
package observer1;
* Created by benjamin on 1/4/16. * 玩游戏的观察者 */ public class PlayGameObserver extends Observer {
public PlayGameObserver(String name, Notification notice) { super(name, notice); }
@Override public void update() { System.out.println(notice.noticeMessage + name + "关闭游戏,继续工作!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
package observer1;
* Created by benjamin on 1/4/16. * 睡觉的观察者 */ public class SleepObserver extends Observer {
public SleepObserver(String name, Notification notice) { super(name, notice); }
@Override public void update() { System.out.println(notice.noticeMessage + name + "停止睡觉,继续工作!"); } }
|
通知者们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
package observer1;
import java.util.ArrayList; import java.util.List;
* Created by benjamin on 1/4/16. * 秘书类 */ public class Secretary extends Notification { private List<Observer> observers = new ArrayList<Observer>();
@Override public void addObserver(Observer observer) { observers.add(observer); }
@Override public void removeObserver(Observer observer) { observers.remove(observer); }
@Override public void notifyObserver() { for (Observer o : observers) { o.update(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
package observer1;
import java.util.ArrayList; import java.util.List;
* Created by benjamin on 1/4/16. * 老板 */ public class Boss extends Notification { private List<Observer> observers = new ArrayList<Observer>();
@Override public void addObserver(Observer observer) { observers.add(observer); }
@Override public void removeObserver(Observer observer) { observers.remove(observer); }
@Override public void notifyObserver() { for (Observer o : observers) { o.update(); } } }
|
进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
package observer1;
* Created by benjamin on 1/4/16. */ public class ObserverTest {
public static void main(String[] args) { Secretary secretary = new Secretary(); PlayGameObserver o1 = new PlayGameObserver("王菲", secretary); SleepObserver o2 = new SleepObserver("谢霆锋", secretary); secretary.addObserver(o1); secretary.addObserver(o2);
secretary.setNoticeMessage("老板回来了!"); secretary.notifyObserver();
System.out.println("------------------------------------");
Boss huhansan = new Boss(); PlayGameObserver p1 = new PlayGameObserver("张柏芝", huhansan); PlayGameObserver p2 = new PlayGameObserver("谢霆锋", huhansan); SleepObserver p3 = new SleepObserver("王菲", huhansan); huhansan.addObserver(p1); huhansan.addObserver(p2); huhansan.addObserver(p3); huhansan.removeObserver(p3);
huhansan.setNoticeMessage("你们不好好工作!"); huhansan.notifyObserver();
* 输出 * 老板回来了!王菲关闭游戏,继续工作! 老板回来了!谢霆锋停止睡觉,继续工作! ------------------------------------ 你们不好好工作!张柏芝关闭游戏,继续工作! 你们不好好工作!谢霆锋关闭游戏,继续工作! */ } }
|
观察者模式的不足
比如我们在使用Eclipse以及Xcode等一些编辑工具的时候,我们可能一个运行可以导致很多窗口隐藏或者出现,而且各个变化都涉及到不同的控件。那么我们要是想通知它们,不可能让它们都去实现Observer接口,因为这些控件早都已经被他们的制造商给封装了。
尽管我们用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象观察者’,也就是说,万一没有了抽象观察者这样的接口,我这通知的功能就完不成了。另外就是每个具体观察者,它不一定要使用‘更新’这个方法,有可能是‘工具箱隐藏’方法,有可能是‘导航栏变色’等。这根本就是不同名的方法,这应该就是不足的地方吧。
运用事件委托delegate机制
委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看做是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。
一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。
刚刚的例子我们加上Event和EventHandler作为委托,修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
package observer1;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
* Created by benjamin on 1/4/16. */ public class Event {
private Object object; private String methodName; private String[] params; private Class[] paramTypes;
public Event(Object object, String methodName, String...params){ this.object = object; this.methodName = methodName; this.params = params; resolveParams(this.params); }
private void resolveParams(String[] params) { int length = params.length; paramTypes = new Class[length]; for (int i = 0; i < length; i ++) { paramTypes[i] = params[i].getClass(); } }
public void invoke() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method method = object.getClass().getMethod(methodName, paramTypes); if (method == null) return; method.invoke(object, params); }
}
|
1 2 3 4 5 6 7 8 9 10
|
package observer1;
* Created by benjamin on 1/4/16. */ public interface Delegate {
void addEvent(Event e); void notifyX() throws Exception; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
package observer1;
import java.util.ArrayList; import java.util.List;
* Created by benjamin on 1/4/16. */ public class EventHandler implements Delegate{ private List<Event> events = new ArrayList<Event>();
public EventHandler(){}
public void addEvent(Event e) { events.add(e); }
public void notifyX() throws Exception { for (Event e : events) { e.invoke(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
package observer1;
* Created by benjamin on 1/4/16. */ public abstract class Notification {
private Delegate delegate ;
public Notification(Delegate delegate) { this.delegate = delegate; }
public abstract void addListener(Object object, String methodName, String...params); public abstract void notifyX();
public Delegate getDelegate() { return this.delegate; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
package observer1;
* Created by benjamin on 1/4/16. * 秘书类 */ public class Secretary extends Notification {
public Secretary(Delegate delegate) { super(delegate); }
@Override public void addListener(Object object, String methodName, String... params) { this.getDelegate().addEvent(new Event(object, methodName, params)); }
@Override public void notifyX() { try { this.getDelegate().notifyX(); } catch (Exception e) { e.printStackTrace(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
package observer1;
* Created by benjamin on 1/4/16. * 老板 */ public class Boss extends Notification {
public Boss(Delegate delegate) { super(delegate); }
@Override public void addListener(Object object, String methodName, String... params) { this.getDelegate().addEvent(new Event(object, methodName, params)); }
@Override public void notifyX() { try { this.getDelegate().notifyX(); } catch (Exception e) { e.printStackTrace(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
package observer1;
* Created by benjamin on 1/4/16. * 玩游戏的观察者 */ public class PlayGameObserver {
private String gamer;
public PlayGameObserver(String name) { gamer = name; }
public void stopGame(String noticeMessage) { System.out.println(noticeMessage + gamer + "关闭游戏,继续工作!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
package observer1;
* Created by benjamin on 1/4/16. * 睡觉的观察者 */ public class SleepObserver {
private String sleeper;
public SleepObserver(String name) { sleeper = name; }
public void stopSleep(String noticeMessage) { System.out.println(noticeMessage + sleeper + "停止睡觉,继续工作!"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
package observer1;
import sun.jvm.hotspot.runtime.Threads;
* Created by benjamin on 1/4/16. */ public class ObserverTest {
public static void main(String[] args) { Delegate delegate = new EventHandler(); Secretary secretary = new Secretary(delegate); PlayGameObserver o1 = new PlayGameObserver("王菲"); SleepObserver o2 = new SleepObserver("谢霆锋"); secretary.addListener(o1, "stopGame", "老板来了!"); secretary.addListener(o2, "stopSleep", "老板来了!");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } secretary.notifyX();
System.out.println("------------------------------------");
Delegate delegate1 = new EventHandler(); Boss huhansan = new Boss(delegate1); PlayGameObserver p1 = new PlayGameObserver("张柏芝"); PlayGameObserver p2 = new PlayGameObserver("谢霆锋"); SleepObserver p3 = new SleepObserver("王菲"); huhansan.addListener(p1, "stopGame", "你们不好好工作!"); huhansan.addListener(p2, "stopGame", "你们不好好工作!"); huhansan.addListener(p3, "stopSleep", "你们不好好工作!");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } huhansan.notifyX();
* 输出 * 老板回来了!王菲关闭游戏,继续工作! 老板回来了!谢霆锋停止睡觉,继续工作! ------------------------------------ 你们不好好工作!张柏芝关闭游戏,继续工作! 你们不好好工作!谢霆锋关闭游戏,继续工作! 你们不好好工作!王菲停止睡觉,继续工作! */ } }
|
最后总结:
1、通知者不知道玩游戏的和睡觉的存在,完全解耦。(功劳归功于Event和EventHandler)
2、老板来后,一次通知,并且通知的消息可以变化,执行不同的方法
3、扩展性很高,再来一个玩纸牌的加上就可以,告诉一下通知者。