当对象间存在一对多的依赖关系时,则使用观察者模式(Observer Pattern)。 比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化。 例如:某些寻找工作的人都对“求职中心”的职业需求信息的变化非常关心,想要跟踪“求职中心”中职业需求信息的变化。一位想知道“求职中心”职业需求信息变化的人,即让求职中心就会及时通知他最新的职业需求信息。如果一个求职者不想继续知道求职中心的职业需求信息,就让求职中心把自己从求职中心的求职者列表中删除,求职中心就不会在继续通知其他职业需求信息。
观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有的“观察者”都得到通知。前面所谈到的求职中心相当于观察者模式的一个具体“主题”;每个“求职者”相当于观察者模式中的一个具体“观察者”。
1.一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2.一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3.一个对象必须通知其他对象,而并不知道这些对象是谁。
4.需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
观察者模式的结构中一般包括四种角色,即:主题,观察者,具体主题,具体观察者。
下面通过一个简单的问题来描述观察者模式中所涉及的各个角色,这个简单问题是:
有一个大学生毕业生和一个归国留学生都希望能够及时知道求职中心最新的职业需求信息。
本问题中,主题接口Subject规定了具体主题需要实现的添加,删除观察者以及通知观察者更新数据的方法,具体如下所示:
package com.xing.observer;
public interface Subject {
//添加观察者
public void addObserver(Observer o);
//删除观察者
public void deleteObserver(Observer o);
//通知所有观察者
public void notifyObservers();
}
观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。hearTelephone方法是模拟观察者接听电话,即接受求职中心的信息。
public interface Observer {
//模拟接听电话
public void hearTelephone(String heardMess);
}
在具体主题的实现类中,采用mess存储更新的求职信息,Boolean类型的changed表示信息是否更新,personList集合表示众多观察者的列表。除了实现了Subject接口里面的方法外,还新增了一个giveNewMess,即发布信息的方法。
package com.xing.observer;
import java.util.ArrayList;
//具体主题
public class SeekJobCenter implements Subject{
String mess;
boolean changed;
ArrayList<Observer> personList;//存放观察者应用的数组线性表
public SeekJobCenter() {
personList=new ArrayList<>();
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;
}
}
}
本问题中,实现观察者接口Observer的类有两个:一个是UniversityStudent类,另一个是HaiGui。UniversityStudent类的实例调用hearTelephone(String heardMess)方法时,会将参数引用的字符串保存到一个文件中。HaiGui类调用hearTelephone(String heardMess)方法时,如果参数引用的字符串包含“程序员”或“软件”,就将信息保存到一个文件中。具体实现分别如下所示:
UniversityStudent.java
package com.xing.observer;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class UniversityStudent implements Observer{
Subject subject;
File myFile;
public UniversityStudent(Subject subject, String fileName) {
this.subject=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.println("我是一个大学生");
System.out.println("我向文件"+myFile.getName()+"写入如下内容");
System.out.println(heardMess);
}catch (IOException exp){
System.out.println(exp.toString());
}
}
}
HaiGui.java
package com.xing.observer;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class HaiGui implements Observer{
Subject subject;
File myFile;
public HaiGui(Subject subject, String fileName) {
this.subject=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.println("我是一个海归");
System.out.println("我向文件"+myFile.getName()+"写入如下内容");
System.out.println(heardMess);
}else{
System.out.println("我是海归,这次的信息没有我需要的信息");
}
}catch (IOException exp){
System.out.println(exp.toString());
}
}
}
Application.java演示了一个大学生和留学生成为求职中心的观察者;当求职中心有新的人才需求信息时,大学生和归国留学生就将得到通知,具体实现如下所示:
package com.xing.observer;
//测试程序
public class Application {
public static void main(String[] args) {
SeekJobCenter center=new SeekJobCenter();//具体主题center
UniversityStudent zhangLin=new UniversityStudent(center,"A.txt");
HaiGui wangHai=new HaiGui(center,"B.txt");
center.giveNewMess("腾讯公司需要10名java程序员");
//通知所有的观察者
center.notifyObservers();
center.giveNewMess("阿里需要5名UI设计师");
center.notifyObservers();
}
}
1.具体主题和具体观察者是松耦合关系。
2.观察者模式满足“开闭原则”。 主题接口(Subject)仅仅依赖于观察者(Observer)接口, 这样,就可以让创建具体主题的类也仅仅是依赖于(Observer),因此,如果增加新的实现观察者接口(Observer)的类,则不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题接口(Subject),如果增加新的实现主题接口(Subject)的类,也不必修改创建具体观察类的代码。