Java观察者模式

1,629 阅读4分钟

被废弃的Observable和Observer

Observable和Observer已经在Java9被弃用。原因如下:

  • 子类有可能会覆盖Observable的一些方法,但却忘记了加上synchronized。导致变得线程不安全。
  • Observable没有实现Serializable,它和它的子类都无法序列化。
  • 所有的Observable都是一样的,如果在Observer想要区分Observable,需要使用instanceof方法,可能还需要将Observable强转成对应的类型做一些操作。 部分解释来自stackoverflow.com/a/46381645/…。但我觉得这不是什么致命的错误,如果程序员使用不当,为何要怪罪API呢?

推模型(Push Model)/拉模型(Pull Model)

推模型:Observable向Observer发送一些除自身外的信息,对应于void update(Observable o, Object arg);中的arg参数包含的信息,往往不为空。
拉模型:Observable除自身(this)外什么也不送出,即arg参数为null。之后由Observer显式的向Observable询问信息。
在实现中,拉模型往往需要将Observable强转成对应的类型。

java.beans

在Observable的文档中,建议使用java.beans来替代Observable。
关于JavaBean属性描述: 摘自:juejin.cn/post/684490…

属性

属性分为四类,即单值(Simple)、索引(Index)、关联(Bound)和约束(Constrained)属性。本节将对这些属性进行详细说明。

单值(simple)属性

单值(Simple)属性是最普通的属性类型,该类属性只有一个单一的数据值,该数据值的数据类型可以是Java中的任意数据类型,包括类和接口等类型。
定义了属性,还需定义对应的访问方法,一般每个单值属性都伴随有一对get/set方法。属性名与和该属性相关的get/set方法名对应。例如如果有一个名为“xxx”的属性,则会有setXxx和getXxx方法。
另外,布尔(Boolean)属性是一种特殊的单值属性,它只有两个允许值:true和false,如果有一个名为“xxx”的布尔属性,则可以通过isX方法访问。

索引(Indexed)属性

如果需要定义一批同类型的属性,使用单值属性就会显得非常烦琐,为解决此问题,JavaBean中提供了索引(Indexed) 属性,索引属性是指JavaBean中数组类型的成员变量。使用与该属性对应的set/get方法可取得数组的值。索引属性通过对应的访问方法设置或取得该属性中某个元素的值,也可以一次设置或取得整个属性的值。

关联(Bound)属性

关联(Bound)属性是指当该种属性的值发生变化时,要通知其他的对象。每次属性值改变时,这种属性就触发一个PropertyChange事件(在Java程序中,事件也是一个对象)。事件中封装了属性名、属性的原值、属性变化后的新值。这种事件传递到其他的Beans,至于接收事件的Beans应做什么动作,由其自己定义。
属性的改变称为JavaBean事件。外部与Java Bean这些事件相关的类对象称为监听者(Listener)。监听者可能只对JavaBean某一属性相关的事件有兴趣,也可能对所有属性相关的事件有兴趣,因此JavaBean提供两类事件监听者注册和注销的方法,即全局事件监听者注册、注销的方法和一般事件监听者注册、注销的方法。

约束(Constrained)属性

Java Bean的属性如果改变时,相关的外部类对象首先要检查这个属性改变的合理性再决定是否接受这种改变,这样的JavaBean属性叫约束(Constrained)属性。当约束属性的改变被拒绝时,改变约束属性的方法产生一个约束属性改变异常(PropertyVetoException),通过这个异常处理,JavaBean约束属性还原回原来的值,并为这个还原操作发送一个新的属性修改通知。
约束属性的改变可能会被拒绝,因此它的setXxx与一般其他JavaBean属性的setXxx也有所不同。约束属性的写方法如下:

public void setXxx(xxxType newXxx)throws PropertyVetoException

如果要发布索引(Indexed)属性的消息,可以使用PropertyChangeSupport来发布IndexedPropertyChangeEvent,使用PropertyChangeListener接受消息。
如果要发布关联(Bound)属性的消息,可以使用PropertyChangeSupport来发布PropertyChangeEvent,使用PropertyChangeListener接受消息。
如果要发布约束(Constrained)属性的消息,可以使用VetoableChangeSupport来发布PropertyChangeEvent,使用VetoableChangeListener接受消息。
VetoableChangeSupport接收到PropertyVetoException受检异常时,会向Listener发布oldValue与newValue互换的消息,执行回退。
PropertyChangeSupport通过PropertyChangeListenerMap管理Listener,PropertyChangeListenerMap管理着一个Map<String, L[]> map.String类型的Key对应相应的属性名称。 如果想要绑定属性和Listener,可以使用PropertyChangeListenerProxyVetoableChangeListenerProxy,它们带有一个propertyName的属性。此时propertyName与Map中的String对应。如果不使用**ListenerProxy,Listener会被绑定至Key为null的L[]中。
PropertyChangeSupport中有一段代码体现了这一特征:

public void firePropertyChange(PropertyChangeEvent event) {
    Object oldValue = event.getOldValue();
    Object newValue = event.getNewValue();
    if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
        String name = event.getPropertyName();

        PropertyChangeListener[] common = this.map.get(null);
        PropertyChangeListener[] named = (name != null)
                    ? this.map.get(name)
                    : null;

        fire(common, event);
        fire(named, event);
    }
}

参考资料: