一、概述
又称为发布-订阅模式。定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。
观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。
二、模式的结构与使用
观察者模式的结构中包括四种角色
1、主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法。比如添加、删除观察者以及通知观察者更新数据的方法
2、观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法
3、具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者
4、具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者
三、观察者模式的UML类图
四、示例(求职中心和求职者)
某些需要找工作的人对“求职中心”的职业需求信息的变化非常关心,很想跟踪“求职中心”中职业需求信息的变化。一位想知道“求职中心”职业需求信息变化的人需要成为求职中心的“求职者”,即让求职中心把自己登记到求职中心的“求职者”列表中,当一个人成为求职中心的求职者后,求职中心就会及时通知他最新的职业需求信息。如何一个求职者不想继续知道求职中心的职业需求信息,就让求职中心把自己从求职中心的求职者列表中删除,求职中心就不会再通知他职业需求信息
1、主题
本问题中,主题接口Subject规定了具体主题需要实现的添加、删除观察者以及通知观察者更新数据的方法。代码如下:
public interface Subject {
public void addObserver(Observer o);
public void deleteObserver(Observer o);
public void notifyObservers();
}
2、观察者
观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。本问题中,观察者接口规定的方法是:hearTelephone(),即要求具体观察者都通过实现该方法来更新数据。代码如下:
public interface Observer {
public void hearTelephone(String hearMess);
}
3、具体主题
规定了具体主题需要实现的通知观察者更新数据的方法,具体主题通过实现notifyObservers()方法来通知具体观察者,实现的方式是遍历具体主题中用来存放观察者引用的集合,并让集合中的每个具体观察者执行观察者接口规定更新数据的方法。
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++) {
Observer observer = personList.get(i);
observer.hearTelephone(mess);
}
changed = false;
}
}
public void giveNewMess(String str){
if(str.equals(mess)){
changed = false;
}else{
mess = str;
changed = true;
}
}
}
4、具体观察者
本问题中,实现观察者接口Observer的类有两个:一个是UniversityStudent类,另一个是HaiGui类。UniversityStudent类的实例调用hearTelephone方法时,会将参数引用的字符串保存到一个文件中。HaiGui类的实例调用hearTelephone时,如果参数引用的字符串中包含有“程序员”或“软件”,就将信息保存到一个文件中。
UniversityStudent.java
public class UniversityStudent implements Observer{
Subject subject;
File myFile;
public UniversityStudent(Subject subject, String fileName) {
this.subject = subject;
subject.addObserver(this);
this.myFile = new File(fileName);
}
@Override
public void hearTelephone(String hearMess) {
try {
RandomAccessFile out = new RandomAccessFile(myFile,"rw");
out.seek(out.length());
byte[] b = hearMess.getBytes();
out.write(b);
System.out.println("我是一个大学生");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(hearMess);
} catch (IOException e) {
e.printStackTrace();
}
}
}
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 hearMess) {
boolean bool = hearMess.contains("java程序员")||hearMess.contains("软件");
try {
if (bool){
RandomAccessFile out = new RandomAccessFile(myFile,"rw");
out.seek(out.length());
byte[] b = hearMess.getBytes();
out.write(b);
System.out.println("我是一个海归");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(hearMess);
}else{
System.out.println("我是海归,这次的信息中没有我需要的信息");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试:
public class Application {
public static void main(String[] args) {
SeekJobCenter center = new SeekJobCenter(); //具体主题center(求职中心)
UniversityStudent zhangsan = new UniversityStudent(center,"d:\\A.txt");//具体观察者zhangsan(求职者)
HaiGui lisi = new HaiGui(center,"d:\\B.txt");//具体观察者lisi(求职者)
center.giveNewMess("腾辉公司需要10个java程序员");//具体主题给出新信息(求职中心发布职业需求信息)
center.notifyObservers();//具体主题通知信息(求职中心向求职者发送信息)
center.giveNewMess("海景公司需要8个动画设计师");
center.notifyObservers();
center.giveNewMess("仁和公司需要9个电工");
center.notifyObservers();
center.giveNewMess("仁和公司需要9个电工"); //信息不是新的
center.notifyObservers();//观察者不会执行更新操作
}
}
运行结果: