0%

观察者模式以及Java中的支持


很久没更新了,今天学习中遇到了一个出现频率极高的设计模式:观察者模式,它的实现还是很简单的,但是其和Java语言有着千丝万缕的关联,这里做一个小总结:

  1. 常见观察者模式
  2. java.util包下所支持的观察者模式(java9中已正式废弃)
  3. java.bean下所提供的观察者模式

1. 常见观察者模式

​ 参见对象间的联动——观察者模式(二),即一个发布者(观察者、监听器)含有一个或订阅者(被观察者)的引用。

2. java.util.Observable & java.util.Observer

由于观察者模式非常常用,故Java在1.0版本中就已经内置了Observable 和Observer接口,分别作为被观察者和观察者。

但其在Java9中已被标记为@deprecated

1
2
3
4
5
6
7
8
9
10
11
12
13
* @deprecated
* This class and the {@link Observer} interface have been deprecated.
* The event model supported by {@code Observer} and {@code Observable}
* is quite limited, the order of notifications delivered by
* {@code Observable} is unspecified, and state changes are not in
* one-for-one correspondence with notifications.
* For a richer event model, consider using the
* {@link java.beans} package. For reliable and ordered
* messaging among threads, consider using one of the concurrent data
* structures in the {@link java.util.concurrent} package.
* For reactive streams style programming, see the
* {@link java.util.concurrent.Flow} API.
*/

There is a quote in there from Josh Bloch as part of JDK-4180466 (“Why is java.util.Observable class not serializable.”) dated February 1999: 引用自:Java’s Observer and Observable are Deprecated in JDK 9

This class is no longer under active development. It is largely unused in the JDK, and has, for the most part, been superseded by the 1.1 Beans/AWT event model. … Observable has fallen into disuse and is no longer under active development.

通过以上,可以总结java.util下这两个接口被弃用的原因:

  1. 不支持序列化 ,Observable未实现Serializable接口,而其内部成员变量私有,子类不能通过继承它来对Observable的成员变量处理m所以子类也不能序列化。
  2. 非线程安全,java.util.Observable不强制要求Observable线程安全,其允许子类覆盖重写Observable的方法,这会导致事件通知无序以及事件通知发生在不同的线程中,影响线程安全。
  3. 支持事件模型的功能简单,只支持事情发生变化的概念,但是不能提供更多哪些内容发生了改变。(在java.beans.PropertyChange..,相关类就给出了相应解决方案)
  4. 在JDK中并未被大规模使用
  5. java.beans包下的新工具取代,尤其使用在了AWT的设计中(参见:基于观察者模式的委派事件模型(DelegationEvent Model, DEM)

3. java.beans.PropertyChangeSupport & java.beans.PropertyChangeListener

  1. java.beans.PropertyChangeListener 对应于DEM模型中的事件监听器(Event Listener)

  2. java.beans.PropertyChangeSupport 一般作为事件源对象(Event Source Bean)的成员变量,存储多个监听器,一般写法为:

    1
    private PropertyChangeSupport listeners = new PropertyChangeSupport(this);
  3. java.beans.PropertyChangeEvent 即事件对象(Event Object)

  4. 流程:客户端调用事件源对象的相应推送方法,该方法内会调用PropertyChangeSupport中所保存的相应PropertyChangeListenerpublic void propertyChange(PropertyChangeEvent evt) 方法,其中PropertyChangeEvent 可由Listener以重载的方式构造。

这里给出相关实现:

Community作为事件源对象,组织多个Play进行活动,Community更改名称以及Community移动两个事件会触发其成员变量listeners中所保存的Play,Play会被调用propertyChange,根据Community产生的相应事件做出改变

  • 事件源对象(Event Source Bean)
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
package structuralPatterns.observerPattern;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

//作为被观察者、发布者
public class Community {
//长官下达命令
private String communityName;
//Play根据任务会修改坐标
private String direction;
//传入的对象为"事件发布者"(Event Source)
private PropertyChangeSupport listeners;

public Community(String communityName) {
this.communityName = communityName;
this.direction = "local";
this.listeners = new PropertyChangeSupport(this);
}


public void setCommunityName(String communityName) {
//会调用PropertyChangeSupport中保存的listener.propertyChange(event);
listeners.firePropertyChange("Community.newCommunityName", this.communityName, communityName);
this.communityName = communityName;
}

public void setDirection(String direction) {
listeners.firePropertyChange("Community.newDirection", this.direction, direction);
this.direction = direction;
}

public void join(PropertyChangeListener player) {
listeners.addPropertyChangeListener(player);
}

public void quit(PropertyChangeListener player) {
listeners.removePropertyChangeListener(player);
}

public String getCommunityName() {
return communityName;
}

public String getDirection() {
return direction;
}

}
  • 事件监听器(Event Source Bean)
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package structuralPatterns.observerPattern;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

//作为观察者、订阅者
public class Player implements PropertyChangeListener {
private String name;
private String fullName;
//坐标
private int x;
private int y;

public Player(String name) {
this.name = name;
this.fullName = "Random." + name;
this.x = 0;
this.y = 0;
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (evt.getPropertyName()) {
case "Community.newCommunityName" -> fullName = evt.getNewValue() + "." + name;
case "Community.newDirection" -> {
switch ((String) evt.getNewValue()) {
case "up" -> y++;
case "down" -> y--;
case "left" -> x--;
case "right" -> x++;
}
}
}
System.out.println(" propertyChanged: " + this);
}
@Override
public String toString() {
return "I`am " + name + ", full name is \"" + fullName + "\". Coordinates is (" + x + "," + y + ").";
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}


}
  • 测试客户端
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
package structuralPatterns.observerPattern;

public class Client {
public static void main(String[] args) {
Community community = new Community("Uestc");

Player mike = new Player("Mike");
Player alice = new Player("Alice");

community.join(mike);
community.join(alice);

System.out.println("Start: " + mike);
System.out.println("Start: " + alice);

System.out.println("# Change1 :community.setCommunityName(\"UestcRun\");");
community.setCommunityName("UestcRun");

System.out.println("# Change2 :community.setDirection(\"up\");");
community.setDirection("up");

System.out.println("# Change3 :community.setCommunityName(\"UestcFly\");");
community.setCommunityName("UestcFly");

System.out.println("# Change4 :community.setDirection(\"left\");");
community.setDirection("left");
}
}
  • 控制台输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Start: I`am Mike, full name is "Random.Mike". Coordinates is (0,0).
Start: I`am Alice, full name is "Random.Alice". Coordinates is (0,0).
# Change1 :community.setCommunityName("UestcRun");
propertyChanged: I`am Mike, full name is "UestcRun.Mike". Coordinates is (0,0).
propertyChanged: I`am Alice, full name is "UestcRun.Alice". Coordinates is (0,0).
# Change2 :community.setDirection("up");
propertyChanged: I`am Mike, full name is "UestcRun.Mike". Coordinates is (0,1).
propertyChanged: I`am Alice, full name is "UestcRun.Alice". Coordinates is (0,1).
# Change3 :community.setCommunityName("UestcFly");
propertyChanged: I`am Mike, full name is "UestcFly.Mike". Coordinates is (0,1).
propertyChanged: I`am Alice, full name is "UestcFly.Alice". Coordinates is (0,1).
# Change4 :community.setDirection("left");
propertyChanged: I`am Mike, full name is "UestcFly.Mike". Coordinates is (-1,1).
propertyChanged: I`am Alice, full name is "UestcFly.Alice". Coordinates is (-1,1).