观察者模式与java事件处理1

观察者模式

1.1观察者模式概述

       观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称之为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
       Observer本来的意思是“观察者”,但实际Observer角色并非主动地去观察,而是被动地接受来自Subject的通知。因此,Observer模式也被称为发布订阅(Publish-Subscribe)模式,也被称为模型-视图(Model-View)模式,源-监听器(Source-Listener)模式,从属者(Dependents)模式。观察者模式是一种对象行为模式。


图1.观察者模式

1.2观察者模式结构

       观察者模式结构中通常包括观察目标和观察者两个继承层次关系,其结构如图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设计模式(刘伟),图解设计模式 (结城浩,杨文轩)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值