橘子学设计模式之观察者模式

一、简介

观察者模式是设计模式中的“超级模式”,其应用随处可见。包括一些常见的概念像订阅,回调等等。
都会在这个设计模式中出现。下面我们来看一下这个设计模式的一些方法和使用。
先举个例子,我们在日常生活中随处可见的就是红绿灯的这个东西。当司机停车在路口的时候,遇到红灯就停下,遇到绿灯就行驶(不说黄灯了)。
那么当灯的颜色变化的时候,路口的司机会随着这个变化而变化。也就是一个灯变,百个司机随着而变。
这种一发而触百人的情况在软件开发中很常见。而本文的主角观察者设计模式就是使用到这个场景的。
更具体到JAVA这个面向对象的语言环境中,我们实际上可以具体化的描述一下。那就是,它定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象。

二、需求环境

1、需求设计

假设我们有这么一个场景,我们要开发一个联机游戏。是一个类似求生之路那种游戏,你和几个小伙伴组队打。当你受到攻击的时候,要把这个信息发给其他队友得知。
我们一般的思路是你受到攻击就发给队友,但是这个操作太复杂。你想想假如你队里五个人,每个人随时可能受到攻击,然后发给其余四个,这样交错的发送,会很耗时。而且使得每个成员还要持有其他队友的信息数据。这样开销比较大。
但是我们本着计算机行业里面没什么是不能加一层的思路,我们就加入一个控制中心,当队友收到攻击把消息上报中心,由中心来控制发送。然后你的队友再做出反应。
在这里插入图片描述
所以现在就面临一个新问题:
1、受攻击的成员将与控制中心产生联动,战队控制中心还将与其他盟友产 生联动。
2、如何实现对象之间的联动?如何让一个对象的状态或行为改变时,依赖于它的对象能够得到 通知并进行相应的处理?
这就是观察者模式要做的工作。

2、观察者模式概述

观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系, 一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。
在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式定义如下: 观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系, 使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式 的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器 (Source/Listener)模式或从属者(Dependents)模式。
观察者模式是一种对象行为型模式。 观察者模式结构中通常包括观察目标和观察者两个继承层次结构,其结构如图所示:
在这里插入图片描述

在观察者模式结构图中包含如下几个角色:

Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集 合,
一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,
同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。 

ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数 据,
当它的状态发生改变时,向它的各个观察者发出通知;
同时它还实现了在目标类中定义 的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。 

Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,
该接 口声明了更新数据的方法update(),因此又称为抽象观察者。 

ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,
它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;
它实现了在抽象观 察者Observer中定义的update()方法。
通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。

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

三、代码实现

为了实现对象之间的联动,开发人员决定使用观察者模式来进行多人联机对战游戏的设计,其基本结构如图所示:
在这里插入图片描述

1、代码实现需求

  • AllyControlCenter充当目标类,设计成抽象方法,以便扩展, 也就是控制中心
  • ConcreteAllyControlCenter充当具体目标类,
  • Observer充当抽象观察者,也就是玩家
  • Player充当具体观察者。
    这里做个预演,目标类就是上面例子中的控制中心,观察者就是玩家。当目标类变化的时候,观察者类随之变化。当控制中心收到攻击的时候发生变化,此时其余队友感知。

观察类接口

package com.liuwei.obersove.example;


/**
 * @author: YX
 * @description: 抽象观察类
 * @date: 2022-5-21 16:47
 * @version: 1.0
 */
interface Observer {
    String getName();
    void setName(String name);

    //支援盟友方法
    void help();
    // 受到攻击的方法,受到攻击要通知中心,所以这里要持有一个控制中心的参数,
    // 然后实现的时候去用设置的注册中心去调方法通知
    void beAttacked(AllyControlCenter acc);
}

观察者的实现类

package com.liuwei.obersove.example;


/**
 * @author: YX
 * @description: 战队成员类:具体观察者类
 * @date: 2022-5-21 16:48
 * @version: 1.0
 */
public class Player implements Observer {
    private String name;

    public Player(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    //支援盟友方法的实现,就是支援
    @Override
    public void help() {
        System.out.println("坚持住," + this.name + "来救你!");
    }

    // 遭受攻击方法的实现,当遭受攻击时将调用战队控制中心类的通知方法notifyObser
    // 受到攻击,调用注入的注册中心里面的通知方法
    @Override
    public void beAttacked(AllyControlCenter acc) {
        System.out.println(this.name + "被攻击!");
        // 调用通知
        acc.notifyObserver(name);
    }
}

目标类的抽象,也就是注册中心

package com.liuwei.obersove.example;

import java.util.ArrayList;

/**
 * @author: YX
 * @description: 战队控制中心类:目标类,定义为抽象类
 * @date: 2022-5-21 16:50
 * @version: 1.0
 */
abstract class AllyControlCenter {
    protected String allyName; //战队名称
    //定义一个集合用于存储战队成员
    protected ArrayList<Observer> players = new ArrayList<Observer>();

    // set get 方法
    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 + "战队!");
        players.add(obs);
    }

    //注销方法,从监控集合移除
    public void quit(Observer obs) {
        System.out.println(obs.getName() + "退出" + this.allyName + "战队!");
        players.remove(obs);
    }

    //声明抽象通知方法
    public abstract void notifyObserver(String name);
}

目标类的实现类

package com.liuwei.obersove.example;

/**
 * @author: YX
 * @description: 具体战队控制中心类:具体目标类
 * @date: 2022-5-21 16:52
 * @version: 1.0
 */
public class ConcreteAllyControlCenter extends AllyControlCenter{

    // 构造方法
    public ConcreteAllyControlCenter(String allyName) {
        System.out.println(allyName + "战队组建成功!");
        System.out.println("----------------------------");
        this.allyName = allyName;
    }

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

调用客户端

package com.liuwei.obersove.example;

/**
 * @author: YX
 * @description: TODO
 * @date: 2022-5-21 16:54
 * @version: 1.0
 */
public class Client {
    public static void main(String[] args) {
        //定义观察目标对象
        AllyControlCenter acc = new ConcreteAllyControlCenter("金庸群侠");
        //定义四个观察者对象,然后加入注册中心,进了那个集合
        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);

        //某成员遭受攻击,受到攻击了,这里就是一个目标类发生变化,通知其余的观察者
        // 发起注册中心的回调,注册中心的方法在beAttacked里面回调。
        player1.beAttacked(acc);
    }
}

结果

金庸群侠战队组建成功!
----------------------------
杨过加入金庸群侠战队!
令狐冲加入金庸群侠战队!
张无忌加入金庸群侠战队!
段誉加入金庸群侠战队!
杨过被攻击!    #这里目标类动了,下面的观察者触发
金庸群侠战队紧急通知,盟友杨过遭受敌人攻击!
坚持住,令狐冲来救你!
坚持住,张无忌来救你!
坚持住,段誉来救你!

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

2、Spring里面的观察者模式

在spring里面有一些观察者模式的体现。具体就是涉及几个对象,:观察者、被观察主题、订阅者。
观察者( Observer )需要绑定要通知的订阅者( Subscriber ),并且要观察指定的主题( Subject )。这个绑定就是我们上面那个注册中心的传参。
SpringFramework 中,体现观察者模式的特性就是事件驱动和监听器。监听器充当订阅者,监听特定的事件;事件源充当被观察的主题,用来发布事件;IOC 容器本身也是事件广播器,可以理解成观察者。
也可以把 SpringFramework 的事件驱动核心概念划分为 4 个:事件源、事件、广播器、监听器

  • 事件源:发布事件的对象,也就是目标类

  • 事件:事件源发布的信息 / 作出的动作

  • 广播器:事件真正广播给监听器的对象【即 ApplicationContext 】
    ApplicationContext 接口有实现 ApplicationEventPublisher 接口,具备事件广播器的发布事件的能 力
    ApplicationEventMulticaster 组合了所有的监听器,具备事件广播器的广播事件的能力

  • 监听器:监听事件的对象,也就是观察者
    借用掘金小册的一个图。
    在这里插入图片描述

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * @author: YX
 * @description: TODO
 * @date: 2022-5-21 17:22
 * @version: 1.0
 */
@Component
public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!");
    }
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author: YX
 * @description: TODO
 * @date: 2022-5-21 17:25
 * @version: 1.0
 */
public class QuickstartListenerApplication {
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                "com.linkedbear.spring.event.a_quickstart");
        System.out.println("IOC容器初始化完成。。。");
        ctx.close();
        System.out.println("IOC容器关闭。。。");
    }
}

import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @author: YX
 * @description: 注解式监听器
 * @date: 2022-5-21 17:28
 * @version: 1.0
 */
@Component
public class ContextClosedApplicationListener {

    @EventListener
    public void onContextClosedEvent(ContextClosedEvent event) {
        System.out.println("ContextClosedApplicationListener监听到ContextClosedEvent事件!");
    }
}

总结

总结一下,这个观察者模式,我们发布一个事件,也就是常说的目标类变化,当你变化的时候。你要去通知那些监听的人(也就是常说的消费者)。那你怎么要通知呢,其实就是在你目标类那里持有了一个通知的对象,通知的对象里面有监听对象的集合。
在你目标类变化那里去调用这个通知的方法,方法里面过滤你的监听哪些话题的监听者,然后就能对应的发布消息了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值