系统解耦(System decoupling)
观察者模式(Observer)
和其它形式的回调函数(callback)类似,Observer模式也允许你通过挂钩程序(hook point)改变代码。不同之处在于,从本质上说,Observer模式是完全动态的。它经常被用于需要根据其它对象的状态变化来改变自身(状态)的场合,而且它还经常是事件管理系统(event management)的基本组成部分。无论什么时候,当你需要用完全动态的方式分离呼叫源和被呼叫代码的时候,(Observer模式都是你的首选)。(最后一句话好像没写完?)
Observer模式解决的是一个相当常见的问题:当某个对象改变状态的时候,另外一组(与之相关的)对象如何更新它们自己。比如说,Smalltalk里的“model-view”结构,(它是MVC(model-view-controller)结构的一部分),再比如基本与之相当的“文档-视图(document-view)”结构。假设说你有一些数据(也就是“文档”)和多于一个的视图,比如说是一个图表(plot)和一个文本视图。当你改变数据的时候,这两个视图必须知道进而(根据需要)更新它们自己,这也就是Observer模式所要帮你解决的问题。这个问题是如此的常见,以至于它的解决办法已经成了标准java.util库的一部分。
用Java实现observer模式需要用到两种类型的对象,Observable类负责记住发生变化时需要通知哪些类,而不论“状态”改变与否。当被观察对象说“OK,你们(指观察者)可以根据需要更新你们自己了,”Observable类通过调用notifyObservers()方法通知列表上的每个观察者,进而完成这个任务。notifyObservers()是基类Observable的一个方法。
实际上,Observer模式真正变化的有两样东西:观察者(observing objects)的数量和它们如何更新自己。也就是说,observer模式使得你在不必改动其它代码的情况下只针对这两种变化更改代码。
。。。。。。(作者还没写完?)
Observer实际上是只有一个成员函数的接口类,这个成员函数就是update()。当被观察者决定更新所有的观察者的时候,它就调用update()函数。是否需要传递参数是可选的;即使是没有参数的Update()函数也同样符合observer模式;但是,更通常的做法是让被观察者(通过update()函数)把引起更新的对象(也就是它自己? )和其它有用的信息传递给观察者, 因为一个观察者可能会注册到多于一个的被观察者 。这样,观察者对象就不用再费劲查找是哪个被观察者引起的更新,并且它所需要的信息也已经传递过来。
决定何时以及如何发起更新(updating)的那个“被观察者对象”被命名为Observable。
Observable类用一个标志(flag)来指示它自己是否改变。对于比较简单的设计来说,不用flag也是可以的;如果有变化,就通知所有的观察者。如果用Flag的话,你可以使通知的时间延迟,并且由你来决定只在合适的时候通知观察者。但是,请注意,控制flag状态的方法是受保护的(protected),也就是说,只有(Observable类的)派生类可以决定哪些东西可以构成一个变化(constitues a change),而不是由Observer派生类的最终用户来决定。
大多数工作是在notifyObservers()这个方法里完成的。如果没有将Flag置为“已改变”,那notifyObservers()什么也不做;否则,它先清除flag的“已改变”状态,从而避免重复调用notifyObservers()的时候浪费时间。This is done before notifying the observers in case the calls to update() do anything that causes a change back to this Observable object.
然后notifyObservers()方法就遍历它所保存的观察者序列
,并且调用每个观察者的update()成员函数。
初看起来,似乎可以用一个普通的Observable对象来管理更新。但是实际上办不到;为了达到这个效果,你必须继承Observable类,并且在派生类的代码里调用setChanged()方法。它就是用来将flag置为“已改变”的那个成员函数,这么一来,当你调用notifyObservers()的时候所有的观察者都会被准确无误的通知道。在什么地方调用setChanged(),这取决于你程序的逻辑结构。
观察花朵
下面是observer模式的一个例子。
//: observer:ObservedFlower.java
// Demonstration of "observer" pattern.
package observer;
import java.util.*;
import junit.framework.*;
class Flower {
private boolean isOpen;
private OpenNotifier oNotify =
new OpenNotifier();
private CloseNotifier cNotify =
new CloseNotifier();
public Flower() { isOpen = false; }
public void open() { // Opens its petals
isOpen = true;
oNotify.notifyObservers();
cNotify.open();
}
public void close() { // Closes its petals
isOpen = false;
cNotify.notifyObservers();
oNotify.close();
}
public Observable opening() { return oNotify; }
public Observable closing() { return cNotify; }
private class OpenNotifier extends Observable {
private boolean alreadyOpen = false;
public void notifyObservers() {
if(isOpen && !alreadyOpen) {
setChanged();
super.notifyObservers();
alreadyOpen = true;
}
}
public void close() { alreadyOpen = false; }
}
private class CloseNotifier extends Observable{
private boolean alreadyClosed = false;
public void notifyObservers() {
if(!isOpen && !alreadyClosed) {
setChanged();
super.notifyObservers();
alreadyClosed = true;
}
}
public void open() { alreadyClosed = false; }
}
}
class Bee {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Bee(String nm) { name = nm; }
// An inner class for observing openings:
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s breakfast time!");
}
}
// Another inner class for closings:
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
class Hummingbird {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Hummingbird(String nm) { name = nm; }
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s breakfast time!");
}
}
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
public class ObservedFlower extends TestCase {
Flower f = new Flower();
Bee
ba = new Bee("A"),
bb = new Bee("B");
Hummingbird
ha = new Hummingbird("A"),
hb = new Hummingbird("B");
public void test() {
f.opening().addObserver(ha.openObserver());
f.opening().addObserver(hb.openObserver());
f.opening().addObserver(ba.openObserver());
f.opening().addObserver(bb.openObserver());
f.closing().addObserver(ha.closeObserver());
f.closing().addObserver(hb.closeObserver());
f.closing().addObserver(ba.closeObserver());
f.closing().addObserver(bb.closeObserver());
// Hummingbird B decides to sleep in:
f.opening().deleteObserver(
hb.openObserver());
// A change that interests observers:
f.open();
f.open(); // It's already open, no change.
// Bee A doesn't want to go to bed:
f.closing().deleteObserver(
ba.closeObserver());
f.close();
f.close(); // It's already closed; no change
f.opening().deleteObservers();
f.open();
f.close();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(ObservedFlower.class);
}
} ///:~
(观察者)感兴趣的事件是花朵的张开和关闭(open or close)。因为有内部类 (Inner class) 可以用,所以这两种事件可以被分开观察。OpenNotifier 和CloseNotifier都从Observable继承而来,它们都可以使用setChanged () 方法,都可以(作为Observable的子类)传递给需要一个Observable对象的地方。
使用inner class 来定义多个Observer也是非常方便的,对于Bee和Hummingbird(蜂鸟),它们可能需要相互独立的观察花朵的张开和关闭。请注意一下,通过使用inner class这种方法,我们实现了一些通过继承才能得到的好处,而且更进一步,通过inner class你还可以访问外部类的私有数据成员,这是继承所不能做到的。
在Main()函数里,你可以看到observer模式所带来的最大好处:通过向被观察者(Observables)动态的注册和卸载观察者对象(Observers),从而在运行时刻改变被观察者的行为。
研究一下上面那些代码你就会发现,OpenNotifier和CloseNotifier只用到了Observable类的基本接口。(这里似乎作者没写完,感觉不连贯)这就意味着你也可以继承其它完全不同的Observer类;观察者和花朵之间唯一的联系就是Observer接口。
关于observers的一个可视化的例子
下面这个例子很像Thinking in Java第二版第14章ColorBoxes那个例子。在屏幕网格上放上箱子(boxes),用随机选出的颜色给这些箱子着色。除了这些,每个箱子都实现了Observer接口并注册到一个Observable对象。当你单击某个箱子的时候,所有其它的箱子都会被通知到,因为Observable对象自动调用每一个Observer对象的update()函数。在update()函数内部,方块对象会检查一下(位置信息)看自己是否与被单击的那个箱子相邻,如果相邻它就把自己的颜色改的和它一样。
//: observer:BoxObserver.java
// Demonstration of Observer pattern using
// Java's built-in observer classes.
package observer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
// You must inherit a new type of Observable:
class BoxObservable extends Observable {
public void notifyObservers(Object b) {
// Otherwise it won't propagate changes:
setChanged();
super.notifyObservers(b);
}
}
public class BoxObserver extends JFrame {
Observable notifier = new BoxObservable();
public BoxObserver(int grid) {
setTitle("Demonstrates Observer pattern");
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for(int x = 0; x < grid; x++)
for(int y = 0; y < grid; y++)
cp.add(new OCBox(x, y, notifier));
}
public static void main(String[] args) {
int grid = 8;
if(args.length > 0)
grid = Integer.parseInt(args[0]);
JFrame f = new BoxObserver(grid);
f.setSize(500, 400);
f.setVisible(true);
f.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
class OCBox extends JPanel implements Observer {
Observable notifier;
int x, y; // Locations in grid
Color cColor = newColor();
static final Color[] colors = {
Color.BLACK, Color.BLUE, Color.CYAN,
Color.DARK_GRAY, Color.GRAY, Color.GREEN,
Color.LIGHT_GRAY, Color.MAGENTA,
Color.ORANGE, Color.PINK, Color.RED,
Color.WHITE, Color.YELLOW
};
static Random rand = new Random();
static final Color newColor() {
return colors[rand.nextInt(colors.length)];
}
OCBox(int x, int y, Observable notifier) {
this.x = x;
this.y = y;
notifier.addObserver(this);
this.notifier = notifier;
addMouseListener(new ML());
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
notifier.notifyObservers(OCBox.this);
}
}
public void update(Observable o, Object arg) {
OCBox clicked = (OCBox)arg;
if(nextTo(clicked)) {
cColor = clicked.cColor;
repaint();
}
}
private final boolean nextTo(OCBox b) {
return Math.abs(x - b.x) <= 1 &&
Math.abs(y - b.y) <= 1;
}
} ///:~
刚开始看Observable的联机帮助的时候,有一点可能会令你比较困惑,从文档上看似乎你可以使用一个普通的Observable对象来管理更新(updates)。但是实际上这么做不行;你可以试一下――更改BoxObserver类,(直接)创建一个Observable类而不是BoxObservable类,然后看看会发生什么:实际上,什么也不会发生。为了让Update生效,你必须得先继承Observable类,然后在派生类的代码里调用setChanged () 方法。这个方法设置“changed”标志(flag),这就意味着当你调用notifyObservers()的时候,所有的观察者(observers)都会无一例外的被通知到。上面的例子里,setChanged()方法只是简单的在notifyObservers()里被调用,但是你可以用任何(其它)准则来决定什么时候调用setChanged()方法。
BoxObserver类只包括一个Observable对象,就是notifier,每次创建新的OCBox对象的时候,它就会和notifier绑定(tied)。在OCBox内部,无论什么时候你单击鼠标都会触发notifyObservers()方法被调用,然后把被单击对象当作一个参数传递给其它方块对象,这样接收这个消息(通过它们自己的update()方法)的方块对象就知道被单击的是哪个方块,进而决定是否需要改变自己。通过组合使用NotifyObservers () 和update () 方法所提供的代码,你可以实现一些相当复杂的东西。
看起来似乎只能通过notifyObservers()方法在编译期(compile time)决定如何通知观察者对象。但是,如果你更加仔细的看看上面那些代码,你就会发现,无论是对于BoxObservable还是OCBox,只有在创建Observable对象的地方你才会意识到你在和BoxObservable打交道,创建完成之后,所有的工作都是通过Observable所提供的基本接口来完成的。这就意味着,如果你希望改变notification的工作状态(notification behavior),你可以通过继承其它的Observable类,并且在运行时刻交换它们来实现。
Mediator
Sweep coupling under the rug, how is this different from MVC?
MVC has distinct model and view; mediator could be anything. MVC a flavor of mediator
练习
1. 给出一个最简单的Observer-Observable设计。只需要写出两个类所必需的(minimum)那些方法即可。然后创建一个Observable对象和多个Observers对象,并用Observable对象来更新(update)Observers对象。
2. 写一个非常简化的Observer系统,在你的Observable类内部使用java.util.Timer,用以产生需要报告给Observers的事件。用内部类的方法创建几个不同的Observer对象,并把它们注册到Observable对象,当Timer事件触发时,Observers必须得被通知到。
3. 改写BoxObserver.java,把它变成一个简单的游戏。对于被单击的那个方块周围的其它任一方块,和它直接或间接相邻的所有同色方块都要被改为和被单击方块同样的颜色(这句累死了)。你可以通过改变游戏的配置,把它变成多玩家相竞争的游戏,或者你可以记录单个玩家完成游戏(指所有方块变为同一颜色)所用的步数。你还可以限制玩家只能以它第一步选择的那种颜色作为最终结束的颜色。