行为型模式之观察者模式(Java版)

设计模式概述及分类:https://blog.csdn.net/qq_34896730/article/details/105324092

面向对象设计原则:https://editor.csdn.net/md/?articleId=105352240

      在软件系统中对象并不是独立存在的,一个对象行为的改变可能会导致一个或多个其他与之存在依赖关系的对象行为发生改变。观察者模式用于描述对象之间的依赖关系,为实现多个对象之间的联动提供了一种解决方案,它们是一种使用频率非常高的设计模式。

1 观察者模式概述

      “红灯停,路灯行”,在日常生活中交通信号灯管理者城市,指挥者日益拥挤的城市恩德交通。当红灯亮起,来往的汽车将停止;而绿灯亮起,汽车可以继续前行。在这个过程中交通信号灯是汽车(准确地说应该是汽车驾驶员)的观察目标,而汽车是观察者。随着交通信号的变化,汽车的行为也将随之变化,一盏交通信号灯可以指挥多辆汽车。
      在软件开发中有些对象之间也存在类似交通信号灯和汽车之间的关系,一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动,正所谓“触一而牵百发”。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式应运而生,它定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象。
      观察者模式是使用频率较高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对象多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
      观察者模式的定义:定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新。
      观察者模式的别名有发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式。观察者模式是一种对象行为型模式。

2 观察者模式结构与实现

2.1 观察者模式结构

      观察者模式结构中通常包括观察目标和观察者,其结构如图2-1所示。

图2-1 观察者模式结构图
      由图2-1可知,观察者模式包含以下4个角色。

      (1) Subject(目标): 目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
      (2) ConcreteSubject(具体目标): 具体目标是目标类的子类,它通常包含有经常发生改变的数据,当它的状态发生改变时将向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有)。如果无须扩展目标类,则具体目标类可以省略。
      (3) Observer(观察者): 观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
      (4) ConcreteObserver(具体观察者): 在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()实现将自己从目标类的集合中删除。

2.2 观察者模式实现

      观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监听观察目标的状态,以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知并不需要知道谁知它的观察者,可以有任意数据的观察者订阅它并接收通知。
      下面通过演示代码对观察者模式进行进一步分析。首先定义一个抽象目标类Subject,其典型代码如下:

public abstract class Subject{
    //定义一个观察者集合用于存储所有观察者对象
    protected ArrayList observers<Observer> = new ArrayList();
    //注册方法,用于向观察者集合中添加一个观察者
    public void attach(Observer observer){
        observers.add(observer);  
   }
   //注销方法,用于从观察者集合中删除一个观察者
   public void detach(Observer observer){
       observers.remove(observer);
   }
   //声明抽象通知方法
   public abstract void notify();
}

      具体目标类ConcreteSubject是实现了抽象目标类Subject的一个具体子类,其典型代码如下:

public class ConcreteSubject extends Subject{
    //实现通知方法
    public void notify() {
        //遍历观察者集合,调用每一个观察者的响应方法
        for(Object obs:observers){
            ((Observer)obs).update(); 
        }
    }
}

      抽象观察者角色一般定义为一个接口,通常只声明一个update()方法,为不同观察者的更新(响应)香味定义相同的接口,这个方法在其子类中实现,不同的观察者具有不同的响应方法。抽象观察者Observer的典型代码如下:

public interface Observer{
   //声明响应方法
   public void update();
}

      在具体观察者ConcreteObserver中实现了update()方法,其典型代码如下:

public class ConcreteObserver implements Observer{
   //实现响应方法
   public void update(){
      //具体响应代码 
   }
}

      在有些更加复杂的情况下,具体观察者ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的状态属性,则可以对观察模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。
      如果再具体层之间具有关联关系,系统的扩展性将受到一定影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。
      在客户端代码中首先创建具体目标对象以及具体观察者对象,然后调用目标对象的attach()方法,将这个观察者对象在目标对象中登记,也就是将它加入到目标对象的观察者集合中,代码片段如下:

...
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer);
subject.notify();
...

      客户端在调用目标对象的notify()方法时将调用在其观察者集合中注册的观察者对象的update()方法。

3 观察者模式应用实例

      下面通过应用实例来进一步学习和理解观察者模式。
      1. 实例说明
      在某多人联机对战游戏中,多个玩家可以加入同一个战队组成联盟,当战队中某一成员收到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将作出响应。
      试使用观察者模式设计并实现该过程,以实现战队成员之间的联动。
      2. 实例分析及类图
      通过分析不难发现在该系统中战队成员之间的联动过程可以简单描述如下:
      联盟成员受到攻击->发送通知给盟友->盟友作出响应
      如果按照上述思路来设计系统,一个战队联盟成员在受到攻击时性需要通知他的每一个盟友,每一个联盟成员都需要持有其他所有盟友的信息,这将导致系统开销较大,因此可以引入一个新的角色——指挥部(战队控制中心)来负责维护和管理每个战队中所有成员的信息。当一个联盟成员受到攻击时将向对应的指挥部发送求助信息,指挥部逐一通知每个盟友,盟友再作出响应。
      通过分析,本实例的结构图如图3-1所示。

图3-1 多人联机对战游戏结构图
      在图3-1中AllyControlCenter当抽象目标类,ConcreteAllyControlCenter充当具体目标类,Observer充当抽象观察者、Player充当具体观察者。

      3. 实例代码
      (1) AllyControlCenter: 指挥部(战队控制中心)类,充当抽象目标类。

public abstract class AllyControlCenter{
  protected String allyName;//战队名称
  protected ArrayList<Observer> players = new ArrayList<Observer>();//定义一个集合用于存储战队成员
  public void setAllyName(String allyName) {
      this.allyName = allyName;
  }
  public String getAllyName(){
      return this.allyName; 
  }
  //注册方法
  public void join(Observer obs) {
        System.out.println(obs.getName()+"加入"+this.allyName+"战队!");
     player.add(obs);
  }
  //注销方法
  public void quit(Observer obs) {
     System.out.println(obs.getName()+"退出"+this.allyName+"战队!");
     player.remove(obs);
  }
  //声明抽象通知方法
  public abstract void notifyObserver(String name);
}

      (2) ConcreteAllyControlCenter:具体指挥部类,充当具体目标类。

public class ConcreteAllyControlCenter extends AllyControlCenter{
  public ConcreteAllyControlCenter (String allyName){
      System.out.println(allyName+"战队组建成功!");
      System.out.println("------------------------");
      this.allyName = allyName;
  }
  //实现通知方法
  public void notifyObserver(String name){
     System.out.println(allyName+"战队紧急通知,盟友"+name+"遭受敌人攻击!");
     //遍历观察者集合,调用每一个盟友(自己除外)的支援方法
     for(Object obs:players){
        if(!((Observer)obs).getName().equalsIgnoreCase(name)){
          ((Observer)obs).help();
        }
      }
  }
}

      (3) Observer:抽象观察者类。

public interface Observer{
    public String getName();
    public void setName();
    public void help();//声明支援盟友方法
    public void beAttacked(AllyControlCenter acc);//声明遭受攻击方法
}

      (4) Player:战队成员类,充当具体观察者类。

public class Player implements Observer{
  private String name;
  public Player(String name){
      this.name = name;
  }
  public void setName(String name){
       this.name = name;
  }
  public String getName(){
       return this.name;
  }
  //支持盟友方法的实现
  public void help() {
      System.out.println("坚持住,"+this.name+"来救你!");
  }
  //遭受攻击方法的实现,当遭受攻击时调用战队控制中心类的通知方法notifyObserver
  public void beAttacked(AllyControlCenter acc){
      System.out.println(this.name+"被攻击!");
      acc.notifyObserver(name);
  }
}

      (5) Client:客户端测试类。

public class Client{
   public static void main(String args[]){
    //定义观察目标对象
    AllyControlCenter acc;
    acc = new ConcreteAllyControlCenter("金庸群侠");
    //定义4个观察者对象
    Observer player1,player2,player3,player4;

    player1 = new Player("杨过");
    acc.join(player1);

    player2 = new Player("令狐冲");
    acc.join(player2);
    
    player3 = new Player("张无忌");
    acc.join(player3);

    player4 = new Player("令狐冲");
    acc.join(player4);
    
    //某成员遭受攻击
    player1.beAttacked(acc);
   }
}

      4. 结果及分析

金庸群侠战队组建成功!
-----------------------
学生票:
杨过加入金庸群侠战队!
令狐冲加入金庸群侠战队!
张无忌加入金庸群侠战队!
段誉加入金庸群侠战队!
杨过被攻击!
金庸群侠战队紧急通知,盟友杨过遭受敌人攻击!
坚持住,令狐冲来救你!
坚持住,张无忌来救你!
坚持住,段誉来救你!

      在本实例中实现了两次对象之间的联动,当一个游戏玩家Player对象的beAttacked()方法被调用时将调用其他Player对象的notifyObserver()方法进行处理,而在notifyObserver()方法中将调用其他Player对象的help()方法。Player的beAttacked()方法、AllyControlCenter()的notifyObserver()方法以及Player的help()方法构成了一个联动触发链,执行顺序如下:
      Player.beAttacked()->AllyControlCenter.notifyObserver()->Player.help()。

4 JDK 对观察者模式的支持

      观察者模式在Java语言中占据非常重要的地位。在JDK的java.util包中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持,如图4-1所示。

图4-1 JDK提供的Observable类及Observer接口结构图
      下面对Observer接口和Observable类进行简要说明。

      1.Observer接口
      在java.util.Observer接口中只声明一个方法,它充当抽象观察者,其方法声明代码如下:

void update(Observable o,Object arg);

      2.Observable类
      在java.util.Observable类充当观察目标类,在Observable中定义了一个向量Vector来存储观察者对象,它所包含的方法及说明见表4-2:

表4-2 Observable类所包含的方法及说明
方法名方法描述
Observable构造方法,实例化Vector向量
addObserver(Observer o)用于注册新的观察者到向量中
deleteObserver(Observer o)用于删除向量中的某一个观察者对象
notifyObservers()和notifyObservers(Object arg)通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法
deleteObservers()用于清空向量,即删除向量中所有的观察者对象
setChanged()用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的update()方法
hasChanged()用于测试对象状态是否改变
countObservers()用于返回向量中观察者的数量

      用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。通过使用JDK中的Observer接口和Observable类可以更加方便地在Java语言中应用观察者模式。

5 观察者模式与Java事件处理

      JDK1.0及更早版本的事件模型基于责任链模式,但是这种模型不适合复杂的系统,因此在JDK1.1及以后的各个版本中事件处理模型采用基于观察者模式的委派事件模型(DEM),即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。
      在DEM模型中目标角色(如界面组件)负责发布事件,而观察者角色(事件处理者)可以向目标订阅它所感兴趣的事件。当一个具体目标产生一个事件时它将通知所有订阅者。事件的发布者称为事件源,而订阅者称为事件监听器,在这个过程中还可以通过事件对象来传递与事件相关的信息,可以在事件监听者的实现类中实现事件处理,因此事件监听对象又可以被称为事件处理对象。事件源、事件监听对象(事件处理对象)和事件对象构成了Java事件处理模型的三要素。事件源对象充当观察目标,而事件监听对象充当观察者。
      以按钮单击事件为例,其事件处理流程如下:
      (1)如果用户在GUI中单击一个按钮,将触发一个事件(如ActionEvent类型的动作事件),JVM将产生一个相应的ActionEvent类型的事件对象,在该事件对象中包含了有关事件和事件源的信息,此时按钮是事件源对象。
      (2)将ActionEvent事件对象传递给监听对象(事件处理对象),JDK提供了专门用于处理ActionEvent时间的接口ActionListener,开发人员需要提供一个ActionListener的实现类(如MyActionHandler),实现在ActionListener接口中声明的抽象事件处理方法actionPerformed(),对所发生事件作出相应的处理。
      (3)开发人员将ActionListener接口的实现类(MyActionHandler)对象注册到按钮中,可以通过按钮类的addActionListener()方法来实现注册。
      (4)JVM在触发事件时将调用按钮的fireXXX()方法,在该方法内部将调用注册到按钮中的事件处理对象的actionPerformed()方法,实现对事件的处理。

6 观察者模式与MVC

      在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式,MVC是一种架构模式,它包含3个角色,即模型(Model)、视图(View)和控制器(Controller)。其中,模型可对应于观察者模式中的观察目标,而视图对应与观察者,控制器可充当两者之间的中介者。当模型层的数据发生改变时视图层将自动改变其显示内容。
      

7 观察者模式优/缺点与适用环境

7.1 观察者模式优点

      (1) 可以实现表现层和逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
      (2) 在观察目标和观察者之间建立了一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
      (3) 支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
      (4) 符合开闭原则,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下增加新的观察目标也很方便。

7.2 观察者模式缺点

      (1) 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
      (2) 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能会导致系统崩溃。
      (3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的而仅仅只是知道观察目标发生了变化。

7.3 观察者模式适用环境

      (1) 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立改变和复用。
      (2) 一个对象的改变导致到一个或多个其他对象也发生改变,而并不知道具体对象将发生改变,也不知道这些对象是谁。
      (3) 需要在系统中创建一个触发连,A对象的行为将影响到B对象,B对象的行为将影响到C对象…,可以使用观察者模式创建一种链式触发机制。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值