返回介绍

16.2 观察器范式

发布于 2024-10-15 23:56:35 字数 4243 浏览 0 评论 0 收藏 0

观察器(Observer)范式解决的是一个相当普通的问题:由于某些对象的状态发生了改变,所以一组对象都需要更新,那么该如何解决?在 Smalltalk 的 MVC(模型-视图-控制器)的“模型-视图”部分中,或在几乎等价的“文档-视图结构”中,大家可以看到这个问题。现在我们有一些数据(“文档”)以及多个视图,假定为一张图(Plot)和一个文本视图。若改变了数据,两个视图必须知道对自己进行更新,而那正是“观察器”要负责的工作。这是一种十分常见的问题,它的解决方案已包括进标准的 java.util 库中。

在 Java 中,有两种类型的对象用来实现观察器范式。其中,Observable 类用于跟踪那些当发生一个改变时希望收到通知的所有个体——无论“状态”是否改变。如果有人说“好了,所有人都要检查自己,并可能要进行更新”,那么 Observable 类会执行这个任务——为列表中的每个“人”都调用 notifyObservers() 方法。notifyObservers() 方法属于基础类 Observable 的一部分。

在观察器范式中,实际有两个方面可能发生变化:观察对象的数量以及更新的方式。也就是说,观察器范式允许我们同时修改这两个方面,不会干扰围绕在它周围的其他代码。

下面这个例子类似于第 14 章的 ColorBoxes 示例。箱子(Boxes)置于一个屏幕网格中,每个都初始化一种随机的颜色。此外,每个箱子都“实现”(implement)了“观察器”(Observer)接口,而且随一个 Observable 对象进行了注册。若点击一个箱子,其他所有箱子都会收到一个通知,指出一个改变已经发生。这是由于 Observable 对象会自动调用每个 Observer 对象的 update() 方法。在这个方法内,箱子会检查被点中的那个箱子是否与自己紧邻。若答案是肯定的,那么也修改自己的颜色,保持与点中那个箱子的协调。

//: BoxObserver.java
// Demonstration of Observer pattern using
// Java's built-in observer classes.
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 Frame {
  Observable notifier = new BoxObservable();
  public BoxObserver(int grid) {
    setTitle("Demonstrates Observer pattern");
    setLayout(new GridLayout(grid, grid));
    for(int x = 0; x < grid; x++)
      for(int y = 0; y < grid; y++)
        add(new OCBox(x, y, notifier));
  }   
  public static void main(String[] args) {
    int grid = 8;
    if(args.length > 0)
      grid = Integer.parseInt(args[0]);
    Frame f = new BoxObserver(grid);
    f.setSize(500, 400);
    f.setVisible(true);
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
  }
}

class OCBox extends Canvas implements Observer {
  Observable notifier;
  int x, y; // Locations in grid
  Color cColor = newColor();
  static final Color[] colors = { 
    Color.black, Color.blue, Color.cyan, 
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta, 
    Color.orange, Color.pink, Color.red, 
    Color.white, Color.yellow 
  };
  static final Color newColor() {
    return colors[
      (int)(Math.random() * 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 paint(Graphics  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 对象来管理更新。但这种说法是不成立的;大家可自己试试——在 BoxObserver 中,创建一个 Observable 对象,替换 BoxObservable 对象,看看会有什么事情发生。事实上,什么事情也不会发生。为真正产生效果,必须从 Observable 继承,并在衍生类代码的某个地方调用 setChanged()。这个方法需要设置“changed”(已改变)标志,它意味着当我们调用 notifyObservers() 的时候,所有观察器事实上都会收到通知。在上面的例子中,setChanged() 只是简单地在 notifyObservers() 中调用,大家可依据符合实际情况的任何标准决定何时调用 setChanged()。

BoxObserver 包含了单个 Observable 对象,名为 notifier。每次创建一个 OCBox 对象时,它都会同 notifier 联系到一起。在 OCBox 中,只要点击鼠标,就会发出对 notifyObservers() 方法的调用,并将被点中的那个对象作为一个参数传递进去,使收到消息(用它们的 update() 方法)的所有箱子都能知道谁被点中了,并据此判断自己是否也要变动。通过 notifyObservers() 和 update() 中的代码的结合,我们可以应付一些非常复杂的局面。

在 notifyObservers() 方法中,表面上似乎观察器收到通知的方式必须在编译期间固定下来。然而,只要稍微仔细研究一下上面的代码,就会发现 BoxObserver 或 OCBox 中唯一需要留意是否使用 BoxObservable 的地方就是创建 Observable 对象的时候——从那时开始,所有东西都会使用基本的 Observable 接口。这意味着以后若想更改通知方式,可以继承其他 Observable 类,并在运行期间交换它们。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文