观察者模式

概念

定义对象间的一种一对多的关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并自动更新。

概述

在许多设计中,经常涉及多个对象都对一个对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化。例如,某些寻找工作的人对求职中心的职业需求信息的变化非常关心,这时求职者可以登记到求职中心的“求职者”列表中,求职中心就会及时通知他所需要的最新的职业需求信息。

观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有―个称作“主题”的对象和若干称作“观察者”的对象,“主题”和“观察者”之间是一种一对多的依赖关系,当主题的状态发生变化时,所有观察者都得到通知。前面所述的“求职中心”相当于观察者模式的“主题”;每个“求职者”相当于观察者模式的一个具体“观察者”。

结构与使用

观察者模式的结构中包括四种角色。

  • 主题(Suject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及观察者更新数据的方法。

  • 观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。

  • 具体主题(ConcreteSuject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。

  • 具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。

UML 类图

observerPattern.jpg

结构描述

1.主题

主题接口规定了具体主题需要实现的添加、删除观察者以及通过观察者更新数据的方法。

1
2
3
4
5
public interface Subject {
public void addObserver(Observer o);
public void deleteObserver(Observer o);
public void notifyObservers();
}

2.观察者

观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。对于本问题具体观察者都通过实现hearTelephone()方法来更新数据。

1
2
3
public interface Observer {
public void hearTelephone(String heardMess);
}

3.具体主题

主题接口规定了具体主题需要实现的通知观察者更新数据的notifyObserver()方法,具体主题通过实现该方法来通知具体观察者。

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
public class SeekJobCenter implements Subject {
String mess;
boolean changed;
ArrayList<Observer> personList; //存放观察者引用的数组线性表
public SeekJobCenter() {
personList = new ArrayList<Observer>();
mess = "";
changed = false;
}
@Override
public void addObserver(Observer o) {
if (!personList.contains(o)) {
personList.add(o);
}
}

@Override
public void deleteObserver(Observer o) {
if (personList.contains(o)) {
personList.remove(o);
}
}

@Override
public void notifyObservers() {
if (changed) { //通知所有观察者
for (int i = 0; i < personList.size(); i++) {
personList.get(i).hearTelephone(mess);
}
changed = false;
}
}
public void giveNewMess(String str) {
if (str.equals(mess)) {
changed = false;
} else {
mess = str;
changed = true;
}
}
}

4.具体观察者

本问题中。实现观察者接口Observer的类有两个:一个是UnivercityStudent类,另一个是HaiGui。 UniversityStudent类的实例调用hearTelephone(String heardMess)方法时,会将参数引用的字符串保存到一个文件中。HaiGui类的实例调用hearTelephone(String heardMess)方法时,如果参数引用的字符串中包含有“java”或“软件”,就将信息保存到一个文件中。UniversityStudent 和HaiGui类的代码如下:

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
//UnivercityStudent.java
public class UniversityStudent implements Observer {
Subject subject;
File myFile;
public UniversityStudent(Subject subject, String fileName) {
this.subject = subject;
subject.addObserver(this);
myFile = new File(fileName);
}
@Override
public void hearTelephone(String heardMess) {
try {
RandomAccessFile out = new RandomAccessFile(myFile, "rw");
out.seek(out.length());
byte[] b = heardMess.getBytes();
out.write(b);
System.out.print("我是一个大学生,");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(heardMess);
} catch (Exception e) {
System.out.println(e.toString());
}
}

}

//HaiGui.java
public class HaiGui implements Observer {
Subject subject;
File myFile;
public HaiGui(Subject subject, String fileName) {
this.subject = subject;
subject.addObserver(this);
myFile = new File(fileName);
}
@Override
public void hearTelephone(String heardMess) {
try {
boolean boo = heardMess.contains("java") || heardMess.contains("软件");
if (boo) {
RandomAccessFile out = new RandomAccessFile(myFile, "rw");
out.seek(out.length());
byte[] b = heardMess.getBytes();
out.write(b);
System.out.print("我是一个海归,");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(heardMess);
} else {
System.out.println("我是海归,这次信息中没有我需要的信息。");
}
} catch (IOException e) {
System.out.println(e.toString());
}
}
}

模式的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
SeekJobCenter center = new SeekJobCenter(); //具体主题center
UniversityStudent zhang = new UniversityStudent(center, "A.txt");
HaiGui wang = new HaiGui(center, "B.txt");
center.giveNewMess("腾辉公司需8个java程序员");
center.notifyObservers();
center.giveNewMess("仁和公司需要9个动画设计师");
center.notifyObservers();
center.giveNewMess("xx公司需要4个电工");
center.notifyObservers();
center.giveNewMess("xx公司需要4个电工"); //信息不是最新的
center.notifyObservers(); //观察者不会执行更新操作
}

优点

  • 具体主题和具体观察者是松耦合关系。由于具体主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题只是知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类,同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者只是知道它依赖的主题是实现主题(Subject)接口的某个类的实例,但不需要知道具体是哪个类。

  • 观察模式满足“开闭原则”。主题(Subject)接口仅仅依赖于观察者(Observer)接口,这样,就可以让创建具体主题的类也仅仅是依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改创建具体主题的类的代码,同样,创建具体观察者的类仅仅依赖于主题(Subject)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码。

使用场景

  • 当以一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。

  • 当一个对象的数据更新时,这个对象也要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据。

举例

  1. 观察者与多主题
  2. 统计文本中的单词和数字

欢迎关注我的其它发布渠道