观察者模式
1.1观察者模式概述
观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称之为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
Observer本来的意思是“观察者”,但实际Observer角色并非主动地去观察,而是被动地接受来自Subject的通知。因此,Observer模式也被称为发布订阅(Publish-Subscribe)模式,也被称为模型-视图(Model-View)模式,源-监听器(Source-Listener)模式,从属者(Dependents)模式。观察者模式是一种对象行为模式。
1.2观察者模式结构
观察者模式结构中通常包括观察目标和观察者两个继承层次关系,其结构如图2所示。
观察者模式包含以下四个角色。
(1)Subject(目标):目标又被称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
(2)ConcreteSubject(具体目标):具体目标是目标类的子类,它通常包含有经常发生改变的数据,当它的状态发生改变时将向他的各个观察者发出通知;同时它还实现了在目标类中定义的抽象逻辑业务方法(如果有)。如果无需扩展目标类,则具体目标类可以省略。
(3)Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
(4)ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Oberver中定义的update()方法。通常在实现时可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。
1.3观察者模式实现
观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察这都将得到通知。作为对这个通知的相应,每个观察者都将监视观察目标的状态,以使其状态与目标状态同步,这种交互也称为发布-定义(Publish-Subscribe)。观察目标是通知的发布者,他发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接受通知。
下面通过演示代码对观察者模式进一步分析。首先定义一个抽象目标类Subject,典型代码如下:
package ObserverModel;
import java.util.ArrayList;
import java.util.List;
public abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected List<Observer> observers=new ArrayList<>();
//注册方法,用于向观察者集合添加一个观察者对象
public void attach(Observer observer){
observers.add(observer);
}
//注销方法,用于从观察者集合中删除一个观察者对象
public void detach(Observer observer){
observers.remove(observer);
}
//声明抽象通知方法
public abstract void notifyObservers();
}
}
具体目标类ConcretrSubject是实现了抽象目标类Subject的一个子类,其典型代码如下:
package ObserverModel;
public class ConcreteSubject extends Subject{
//实现通知方法
@Override
public void notifyObservers(){
//遍历观察者集合,调用每一个观察者的响应方法
for(Observer obs:observers){
obs.update();
}
}
}
抽象观察者角色一般定义为一个接口,通常只声明一个update()方法,为不同观察者的更新(响应)行为定义相同的接口,这个方法在其子类中实现,不同的观察者具有不同的响应方法。抽象观察者Observer的典型代码如下:
package ObserverModel;
public interface Observer {
//声明响应方法
public void update();
}
在具体观察者ConcreteObserver中实现了update方法,其典型代码如下:
package ObserverModel;
public class ConcreteObserver implements Observer{
//实现响应方法
@Override
public void update() {
//具体响应代码
}
}
在有些复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无需维持对象引用。
如果在具体层之间具有关联关系,系统的扩展性将受到一定程度的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则,但是如果原有观察者类无须管理新增的具体目标,则系统扩展性不受影响。
在客户端代码中首先创建具体目标对象以及具体观察者对象,然后调用目标对象的attach方法,将这个观察者对象在目标对象中登记,即将它加入到目标对象的观察者集合中,代码片段如下:
package ObserverModel;
public class Test {
public static void main(String[] args){
Subject subject=new ConcreteSubject();
Observer observer=new ConcreteObserver();
subject.attach(observer);
subject.notifyObservers();
}
}
1.4 观察者模式应用实例
下面我们来看一段使用了观察者模式的示例程序。这是一段简单的示例程序,观察者将观察一个会生成数值的对象,并将它生成的数值结果显示出来。不过,不同的观察者的显示方式不一样。DigitObserver会以数字形式显示数值,而GraphObserver则会以简单的图示形式来显示数值。
示例程序类图
Observer接口:
Observer接口是表示“观察者”的接口。具体的观察者会实现这个接口。用于生成数值的NumberGenerator会调用Observer的update方法,将“生成的数值发生了变化,请更新显示内容”的通知发给Observer。
package observermodelexample;
public interface Observer {
public void Update();
}
NumberGenerator类
NumberGenerator类是用于生成数值的抽象类。生成数值的方法( execute方法)和获取数值的方法( getNumber方法)都是抽象方法,需要子类去实现。
observers字段中保存有观察NumberGenerator的Observer们。
addObserver方法用于注册Observer,deleteObserver方法用于删除Observer,notifyObservers方法会向所有的Observer发送通知,告诉它们“我生成的数值发生了变化,请更新显示内容”。该方法会调用每个Observer的update方法。
package observermodelexample;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public abstract class NumberGenerator {
private List<Observer> observers=new ArrayList<>();
public void addObserver(Observer observer){
observers.add(observer);
}
public void deleteObserver(Observer observer){
observers.remove(observer);
}
public void notifyObservers(){
Iterator<Observer> it=observers.iterator();
while(it.hasNext()){
Observer o=(Observer) it.next();
o.update(this);
}
}
public abstract int getNumber();
public abstract void execute();
}
RandomNumberGenerator类
RandomNumberGenerator类是NumberGenerator的子类,它会生成随机数。random字段中保存有java. util. Random类的实例(即随机数生成器)。而number字段中保存的是当前生成的随机数。getNumber方法用于获取number字段的值。execute方法会生成20个随机数(0 ~ 49的整数),并通过notifyObservers方法把每次生成结果通知给观察者。这里使用的nextInt方法是java.util. Random类的方法,它的功能是返回下一个随机整数值(取值范围大于0,小于指定值)。
package observermodelexample;
import java.util.Random;
public class RandomNumberGenerator extends NumberGenerator{
private Random random=new Random();
private int number;
@Override
public int getNumber() {
return number;
}
@Override
public void execute() {
for(int i=0;i<10;i++){
number=random.nextInt(50);
notifyObservers();
}
}
}
DigitObserver类
DigitObserver类实现了Observer接口,它的功能是以数字形式显示观察到的数值。它的update方法接收NumberGenerator的实例作为参数,然后通过调用NumberGenerator类的实例的getNumber方法可以获取到当前的数值,并将这个数值显示出来。为了能够让大家看清它是如何显示数值的,这里我们使用Thread. sleep来降低了程序的运行速度。
package observermodelexample;
public class DigitObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
System.out.println("DigitObserver:"+numberGenerator.getNumber());
try{
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
}
}
GraphObserver
Graphobserver类也实现了Observer接口。该类会将观察到的数值以*****这样的简单图示的形式显示出来。
package observermodelexample;
public class GraphObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
System.out.println("GraphObserver:");
int count=numberGenerator.getNumber();
for(int i=0;i<count;i++) System.out.print("*");
System.out.println("");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Main类
Main类成了一个RandomNumbe rGenerator类的实例和两个观察者,其中observerl是DigitObserver类的实例,observer2 是GraphObserver类的实例。在使用addObserver注册观察者后,它还会调用generator . execute方法生成随机数值。
package observermodelexample;
public class Main {
public static void main(String[] args){
NumberGenerator numberGenerator=new RandomNumberGenerator();
Observer observer1=new DigitObserver();
Observer observer2=new GraphObserver();
numberGenerator.addObserver(observer1);
numberGenerator.addObserver(observer2);
numberGenerator.execute();
}
}
程序运行结果示例
Tips:可以把具体观察者的独特行为单独封装成一个方法,然后update调用该方法。
修改后的DigitObserver类:
package observermodelexample;
public class DigitObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
printNumber(numberGenerator);
}
private void printNumber(NumberGenerator numberGenerator){
System.out.println("DigitObserver:"+numberGenerator.getNumber());
try{
Thread.sleep(1000);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
修改后的GraphObserver类:
package observermodelexample;
public class GraphObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
printGraph(numberGenerator);
}
void printGraph(NumberGenerator numberGenerator){
System.out.println("GraphObserver:");
int count=numberGenerator.getNumber();
for(int i=0;i<count;i++) System.out.print("*");
System.out.println("");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Main类不变,运行结果如下(部分截图):
更新后的类图如下:
这种做法更有助于保持代码清洁、提高可读性、便于测试和增强复用性。虽然引入了额外的方法调用,但这在大多数应用中都是可以接受的性能开销。
参考:Java设计模式(刘伟),图解设计模式 (结城浩,杨文轩)