Observer (观察者) 模式

17.1 Observer模式

  Observer 的意思是 “进行观察的嗯”,也就是 “观察者” 的意思。
  在 Observer 模式中,当观察对象的状态发生变化时,会通知观察者。Observer 模式适用于根据对象状态进行相应处理的场景。

17.2 示例程序

  这是一段简单的示例程序,观测者将观察一个会生成数值的对象,并将它生成的数值结果显示出来。不过,不同的观测者的显示方式不一样。DigitObserver 会以数字形式显示数值,而 GraphObserver 则会以简单的图示形式来显示数值。

名字说明
Observer表示观测者的接口
NumberGenerator表示生成数值的对象的抽象类
RandomNumberGenerator生成随机数的类
DigitObserver表示以数字形式显示数值的类
GraphObserver表示以简单图示形式显示数值的类
Main测试程序行为的类

示例程序的类图

示例程序类图
|| Observer 接口

  Observer 接口是表示 “观察者” 的接口。具体的观测者会实现该接口。
  注意,这个 Observer 接口是为了方便了解 Observer 的示例程序而编写的,它与 java 类库中的 java.util.Observer 接口不同。
  用于生成数值的 NumberGenerator 类会调用 update 方法,将 “生成的数值发生了变化,请更新显示内容” 的通知发生给 Observer。

/**
* 自定义观察者接口.
*/
public interface Observer {

    /**
     * 发生变化时,由 Generator 发送通知.
     * @param generator
     */
    void upadate(NumberGenerator generator);
}
|| NumberGenerator 类

  NumberGenerator 类是用于生成数值的抽象类。生成数值的方法(execute)和获取数值的方法(getNumber)都是抽象方法,需要子类去实现。
  observers 字段中保存有观察 NumberGenerator 的 Observer 们。
  addObserver 方法用于注册 Observer,而 deleteObserver 方法用于删除 Observer。
  notifyObservers 方法会向所有的 Observer 发生通知,告诉它们 “我生成的数值发生了变化,请更新显示内容”。该方法会调用每个 Observer 的 update 方法。

/**
* 用于生成数值的抽象对象.
*/
public abstract class NumberGenerator {
    private List<Observer> observers = new ArrayList<>();

    public void addObserer(Observer observer) {
        observers.add(observer);
    }

    public  void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    // 通知观查者
    public void notifyObservers() {
        observers.forEach(e -> e.upadate(this));
    }

    abstract int getNumber();
    abstract void execute();
}
|| RandomNumberGenerator 类

  RandomNumberGenerator 类是 NumberGenerator 的子类,它会生成随机数。
  execute 方法会生成一定数量的随机数,并通过 notifyObservers 方法把每次生成结果通知给观察者。

/**
* 随机数实现类.
*/
public class RandomNumberGenerator extends NumberGenerator {

    private Random random = new Random();
    private int number;

    @Override
    int getNumber() {
        return number;
    }

    @Override
    void execute() {
        for (int i = 0; i < 20; i++) {
            number = random.nextInt(50);
            notifyObservers();
        }
    }
}
|| DigitObserver 类

  DigitObserver 类实现了 Observer 接口,它的功能是以数字形式显示观察到的数值。为了能够看清它是如何显示数值的,我们使用线程睡眠的方法降低了程序的运行速度。

/**
* 以数字形式显示观察到的数值.
*/
public class DigitObserver implements Observer {
    @Override
    public void upadate(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            TimeUnit.MICROSECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
|| GraphtObserver 类

  GraphObserver 类也实现 Observer 接口。该类会将观察到的数值以 ***** 这样的简单图示的形式显示出来。

/**
* 将观察到的数值以 ***** 这样简单的形式显示出来.
*/
public class GraphObserver implements Observer {

    @Override
    public void upadate(NumberGenerator generator) {
        System.out.print("GraphObserver:");
        int count = generator.getNumber();
        for (int i = 0; i < count; i++) {
            System.out.print('*');
        }
        System.out.println();
        try {
            TimeUnit.MICROSECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
|| Main 类

  Main 类会生成一个 RandomNumberGenerator 类的实例和两个观测者,其中 observer1 是 DigitObserver 类的实例,observer2 是 GraphObserver 类的实例。

public class Main {

    public static void main(String[] args) {
        RandomNumberGenerator generator = new RandomNumberGenerator();
        Observer digitObserver = new DigitObserver();
        Observer graphObserver = new GraphObserver();
        generator.addObserer(digitObserver);
        generator.addObserer(graphObserver);
        generator.execute();
    }
}

程序运行结果:

DigitObserver:28
GraphObserver:****************************
DigitObserver:44
GraphObserver:********************************************
DigitObserver:39
GraphObserver:***************************************
DigitObserver:18
GraphObserver:******************
DigitObserver:19
GraphObserver:*******************
DigitObserver:16
GraphObserver:****************
DigitObserver:34
GraphObserver:**********************************
...
17.3 Observer 模式中的登场角色

  在 Observer 模式中有以下登场角色。
  ◆ Subject (观察对象)
  Subject 角色表示观察对象。Subject 角色定义了注册观察者和删除观察者的方法。此外,它还是声明了 “获取现在的状态” 的方法。在示例程序中,由 NumberGenerator 类扮演此角色。
  ◆ ConcreteSubject (具体的观察对象)
  ConcreteSubject 角色表示具体的被观察对象。当自身状态发生变化后,它会通知所有已经注册的 Observer 角色。在示例程序中,由 RandomNumberGenerator 类扮演此角色。
  ◆ Observer (观察者)
  Observer 角色负责接收来自 Subject 角色的状态变化的通知。为此,它声明了 update 方法。在示例程序中,由 Observer 接口扮演此角色。
  ◆ ConcreteObserver (具体的观察者)
  ConcreteObserver 角色表示具体的 Observer。当它的 update 方法被调用后,会去获取要观察的对象的最新状态。在示例程序中,由 DigitObserver 类和 GraphObserver 类扮演此角色。
Observer 模式的类图

Observer 模式的类图
17.4 拓展思路的要点
|| 这里也出现了可替换性

  使用设计模式的目的之一就是使类成为可复用的组件。
  在 Observer 模式中,有带状态的 ConcreteSubject 角色和接收状态变化通知的 ConcreteObserver 角色。连接这两个角色的就是它们的接口 Subject 角色和 Observer 角色。
  一方面 RandomNumberGenerator 类并不知道,也无需在意正在观察自己的具体实例。不过,它知道在它的 observers 字段中所保存的观察者们都实现了 Observer 接口。因为这些实例都是通过 addObserver 方法注册的,确保了它们一定都实现了 Observer 接口,一定可以调用它们的 update 方法。
  另一方面,DigitObserver 类也无需在意自己正在观察的具体实例。DigitObserver 类知道它们是 NumberGenerator 类的子类的实例,并持有 getNumber 方法。
  利用抽象类和接口从具体类中抽出抽象方法
  将实例作为参数传递至类中,或者在类的字段中保存实例时,不使用具体类型,而是使用抽象类型和接口
  这样的实现方式可以帮助我们轻松的替换具体类。

|| Observer 的顺序

  Subject 角色中注册有多个 Observer 角色。通常在设计 ConcreteObserver 角色的类时,需要注意这些 Observer 的 update 方法的调用顺序,不能因为 update 方法的调用顺序发生改变而产生问题。
  当然,通常只要保持各个类的独立性,就不会发生上面这种类的依赖关系混乱的问题。

|| 当 Observer 的行为会对 Subject 产生影响时

  在本章的示例程序中, RandomNumGenerator 类会在自身内部生成数值,调用 update 方法。不过,在通常的 Observer 模式中,也可能是其他类触发 Subject 角色调用 update 方法。例如,在 GUI 应用程序中,多数情况下是用户按下按钮后会触发 update 方法被调用。
  当然, Observer 角色也有可能会触发 Subject 角色调用 update 方法。这是,如果稍不留神,就可能会导致方法被循环调用。

|| 传递更新信息的方式

  NumberGenerator 利用 update 方法告诉 Observer 自己的状态发生了更新。传递给 update 方法的参数只有一个,就是 NumberGenerator 的实例自身。Observer 会在 update 方法中调用该实例的 getNumber 来获取足够的数据。
  不过在示例程序中,update 方法接收到的参数中并没有被更新的数值。也就是说,update 方法的定义可能不是如下(1)中这样,而是如下(2)中这样,或者更简单的(3)这样:

void update(NumberGenerator generator); (1)
void update(NumberGenerator generator, int number); (2)
void update(int number); (3)

  (1) 只传递了 Subject 角色作为参数。Observer 角色可以从 Subject 角色中获取数据;
  (2) 除了传递 Subject 角色以外,还传递了 Observer 所需的数据(即更新信息)。这样就省去了 Observer 自己获取数据的麻烦。不过,这样做的话,Subject 角色就知道了 Observer 所要进行的处理的内容了。
  在很复杂的程序中,让 Subjcet 角色知道 Observer 角色所要进行的处理会让程序变的缺少灵活性。例如,假如现在我们需要传递上次的数值和当前数值之间的差值,那么我们就必须在 Subject 角色中先计算出这个差值。因此,我们需要综合考虑程序的复杂度来设计 update 方法的参数的最优方案。
  (3)比(2)简单,省略了 Subject 角色。示例程序同样也适用这种实现方式。不过,如果一个 Observer 角色需要观察多个 Subject 角色的时候,此方式就不适用了。这是因为 Observer 角色不知道传递给 update 方法的参数究竟是其中哪个 Subject 角色的值。

|| 从 “观察” 变为 “通知”

  Observer 本来的意思是 “观察者”,但实际上 Observer 角色并非主动地去观察,而是被动地接受来自 Subject 角色的通知。因此, Observer 模式也被成为 Publish-Subscribe (发布-订阅)模式。

|| Model/View/Controller (MVC)

  MVC 中的 Model 和 View 的关系与 Subject 角色和 Observer 角色的关系相对应。 Model 是指操作 “不依赖于显示形式的内部模型” 的部分, View 则是管理 Model “怎样显示” 的部分。通常情况下,一个 Model 对应多个 View。

17.5 延伸阅读:java.util.Observer 接口

  Java 类库中的 java.util.Observer 接口和 java.util.Observable 类就是一种 Observer 模式。
  java.util.Observer 接口中定义了以下方法。

public void update(Observable obj, Object arg)

  而 update 方法的参数则接收到了如下内容:
  Observable 类的实例是被观察的 Subject 角色
  Object 类的实例是附加信息
  与上文中提到的类型(2)相似,我们由此会想 java 已经为我们提供了 Observer 模式了啊,我们直接用就可以了吧。
  话虽如此,但是 Observer 接口和 Observable 类并不好用。因为,传递给 Observer 接口的 Subject 角色必须是 Observable 类型的。但 Java 只能单一继承,也就是说如果 Subject 角色已经是某个类的子类了,那么它将无法继承 Observable 接口。
  

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值