前言

观察者模式也是对象行为模式的一种,又叫做发表-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、 咱们目前用的最多的就是各种MQ(Message Queue)都是基于这个模式的思想来实现的,生产者产生数据放到一个队列中,消费者观察生产者的消息队列的变化,从而接收消息,执行消费者本身的逻辑。

观察者模式

概念介绍

观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象。这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新。

这些观察者之间没有任何关联,可以根据业务需要增加删除观察者,易于系统扩展。

举例

还是来举实际的例子,来介绍设计模式,毕竟设计模式是一种抽象的东西,需要落到真正的实现中才能体现出它的价值。当我们在网上购物时,看到一件自己比较喜欢的商品,但是最近手头有点紧(已经开始吃土了),所以会先关注一下这个商品,一般的购物网站上都会有关注此商品这么一个功能的。为了就是当商品降价打折或是其他变化的时候能够通知到所有关注此商品的顾客。那么我们就以这个功能为例子来使用观察者模式实现一下。

抽象主题类

/**
* 抽象被观察类
*/
@Getter
public abstract class Observable { //观察者集合,存储关注商品的所有顾客
protected List<Observer> observerList = Lists.newArrayList(); /**
* 添加观察者(当一个顾客选择了关注商品时添加到观察者集合中)
* @param observer 观察者
*/
public void attach(Observer observer){
observerList.add(observer);
} /**
* 注销观察者(取消关注商品)
* @param observer 观察者
*/
public void detach(Observer observer){
observerList.remove(observer);
} /**
* 通知观察者的方法
*/
public abstract void notice(Object obj);
}

商品类

/**
* 商品类
*/
@Getter //lombok get方法
@AllArgsConstructor //lombok 以所有属性为参数的构造方法
@NoArgsConstructor //lombok 没有参数的构造方法
public class Product extends Observable { /** 商品名称 */
protected String name;
/** 商品价格*/
protected BigDecimal price; /**
* 商品名称变更
* @param name 商品名称
*/
public void setName(String name){
this.name = name;
//通知观察者
notice(name);
} /**
* 价格变更
* @param price 商品价格
*/
public void setPrice(BigDecimal price){
this.price = price;
//通知观察者
notice(price);
} /**
* 通知观察者的方法
*/
@Override
public void notice(Object obj) {
if(Objects.nonNull(observerList)&&observerList.size()>0){
observerList.forEach((Observer observer) -> observer.update(obj));
}
}
}

抽象观察者类

/**
* 抽象观察者
*/
public abstract class Observer {
/**
* 更新
* @param obj 更新对象
*/
public abstract void update(Object obj); }

名称观察者

/**
* 名称观察者
*/
public class NameObserver extends Observer {
/**
* 更新
* @param obj 更新对象
*/
@Override
public void update(Object obj) {
if(obj instanceof String){
String name = (String) obj;
System.out.println("您关注的商品名称发生了变化,最新的商品名称是"+name);
}
}
}

价格观察者

/**
* 价格观察者
*/
public class PriceObserver extends Observer{ /**
* 更新
*
* @param obj 更新对象
*/
@Override
public void update(Object obj) {
if(obj instanceof BigDecimal){
BigDecimal price = (BigDecimal)obj;
System.out.println("您关注的商品价格发生了变化,最新的商品价格是:"+price);
}
}
}

测试类

public class Test {

    public static void main(String[] args) {

        Product product = new Product("iphoneX",new BigDecimal(8999));
System.out.println("您关注的商品的名称是:"+product.getName()+",价格是:"+product.getPrice());
//创建观察者
NameObserver nameObserver = new NameObserver();
PriceObserver priceObserver = new PriceObserver();
//加入观察者
product.attach(nameObserver);
product.attach(priceObserver);
//产生变化,通知观察者
product.setName("iphoneX Max");
product.setPrice(new BigDecimal(12999));
} }

运行结果:

您关注的商品的名称是:iphoneX,价格是:8999
您关注的商品名称发生了变化,最新的商品名称是iphoneX Max
您关注的商品价格发生了变化,最新的商品价格是:12999

通过上面的运行结果我们就能看出来,当商品名称或价格发生变化时,会通知到相应的观察者,这就是观察者模式的具体应用了。那么通过例子我们也可以看出来观察者模式具体是由哪些角色组成的。

观察者模式的结构

观察者模式结构如下图

在观察者模式中存在如下几种角色:

抽象主题角色(Subject):抽象主题角色把所有的观察者对象的引用保存在一个列表里;每个主题都可以有任何数量的观察者。主题提供一个接口,可以加上或撤销观察者对象;主题角色又被称为被观察者角色。可以用抽象类或接口来实现。

抽象观察者角色(Observer):为所有的具体观察者定义一个接口,在得到通知时更新自己。抽象观察者角色通常是用一个抽象类或一个接口来实现;当然也可以用具体的类来实现。

具体主题角色(ConcreteSubject):具体主题保存对具体观察者对象有用的内部状态,在这种状态改变时,给其观察者发出一个具体的通知,具体主题角色又被称为具体被观察者角色。

具体观察者角色(ConcreteObserver):具体观察者角色用于保存一个指向具体主题对象的引用,和一个与主题的状态相符的状态。具体观察者角色实现抽象观察者角色所要求的更新自己的接口,以便使本身的状态与主题的状态对应。

总结

观察者模式是一种使用频率比较高的设计模式,凡是涉及到一对一或一对多的对象交互场景都可以使用观察者模式。

观察者模式的主要优点

1、观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当观察者角色。

2、观察者模式在观察目标和观察者之间建立一个抽象耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

3、观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

4、观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

观察者模式的主要缺点

1、如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。

2、如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景

1、一个对象的改变将会导致一个或多个对象的改变,不清楚具体有多少对象以及这些被影响的对象是谁的情况。

2、如果有这样一个影响链的情况下也可以使用,例如A的改变会影响B,B的改变会影响C......,可以使用观察者模式设计一个链式触发机制。

想了解更多的设计模式请查看Java设计模式学习记录-GoF设计模式概述

这个是我的个人公众号,文章以后也会同步到公众号上去,欢迎关注。

Java设计模式学习记录-观察者模式的更多相关文章

  1. Java设计模式学习记录-模板方法模式

    前言 模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤. 模板方法模式 概念介绍 模板方法模式,其实是很好理解的,具体 ...

  2. Java设计模式学习记录-状态模式

    前言 状态模式是一种行为模式,用于解决系统中复杂的对象状态转换以及各个状态下的封装等问题.状态模式是将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象的状态可以灵活多变.这样在客户端使 ...

  3. Java设计模式学习记录-备忘录模式

    前言 这次要介绍的是备忘录模式,也是行为模式的一种 .现在人们的智能手机上都会有备忘录这样一个功能,大家也都会用,就是为了记住某件事情,防止以后自己忘记了.那么备忘录模式又是什么样子的呢?是不是和手机 ...

  4. Java设计模式学习记录-迭代器模式

    前言 这次要介绍的是迭代器模式,也是一种行为模式.我现在觉得写博客有点应付了,前阵子一天一篇,感觉这样其实有点没理解透彻就写下来了,而且写完后自己也没有多看几遍,上次在面试的时候被问到java中的I/ ...

  5. Java设计模式学习记录-解释器模式

    前言 这次介绍另一个行为模式,解释器模式,都说解释器模式用的少,其实只是我们在日常的开发中用的少,但是一些开源框架中还是能见到它的影子,例如:spring的spEL表达式在解析时就用到了解释器模式,以 ...

  6. Java设计模式学习记录-命令模式

    前言 这次要介绍的是命令模式,这也是一种行为型模式.最近反正没有面试机会我就写博客呗,该投的简历都投了.然后就继续看书,其实看书也会给自己带来成就感,原来以前不明白的东西,书上已经给彻底的介绍清楚了, ...

  7. Java设计模式学习记录-享元模式

    前言 享元模式也是一种结构型模式,这篇是介绍结构型模式的最后一篇了(因为代理模式很早之前就已经写过了).享元模式采用一个共享来避免大量拥有相同内容对象的开销.这种开销最常见.最直观的就是内存损耗. 享 ...

  8. Java设计模式学习记录-外观模式

    前言 这次要介绍的是外观模式(也称为门面模式),外观模式也属于结构型模式,其实外观模式还是非常好理解的,简单的来讲就是将多个复杂的业务封装成一个方法,在调用此方法时可以不必关系具体执行了哪些业务,而只 ...

  9. Java设计模式学习记录-装饰模式

    前言 装饰模式也是一种结构型模式,主要是目的是相对于类与类之间的继承关系来说,使用装饰模式可以降低耦合度.JDK中有不少地方都使用到了装饰模式,例如Java的各种I/O流,javax.swing包中一 ...

随机推荐

  1. c# 自定义日期的时分秒

    DateTime beginTime = DateTime.Now.Date; 2 Console.WriteLine(beginTime); DateTime endTime = , , ); Co ...

  2. Linux 下 ftp的使用

    最近需要在Linux上搭建FTP服务,通过网上的一些大神学习了一些新知识,在这个做一个总结: Linux 下FTP 为 vsftp (linux red hat)1.FTP配置路径:/etc/vsft ...

  3. h5上传视频文件

    从一开始我就掉坑里了,<input type="file" style="display: block;" id="img-upload&quo ...

  4. 计算机网络四:网卡与MAC地址

    网卡与MAC地址 ㈠网卡 1.网卡定义 网卡是工作在OSI的数据链路层的网络组件,是局域网中连接计算机和传输介质(网线或WIFI信号)的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉 ...

  5. 基于jmeter的性能测试平台(一)分布式jmeter搭建

    (1)概述 一台windows虚拟机作为controller,3台Linux虚拟机作为agent. 第一步是在所有虚拟机上安装JDK,版本最好是一样的,然后就是下载安装jmeter,网上资料很多这里不 ...

  6. 为程序设置多语言界面——C#

    考虑到程序的国际化需求,需要为程序设置多语言界面. 1,新建一个资源文件,名字可以是对应界面+语言代码(MainForm.zh-CN).这样资源文件就会自动添加到对应界面下面. 2,更改界面属性Loc ...

  7. kubernetes CSI 插件机制学习笔记

    前言 最近在极客时间订阅了kubernetes的专栏,这篇文章是想记录一下自己学习 CSI 插件机制 (container-storage-interface) 的过程,加深一下记忆. 准备工作 老师 ...

  8. Codeforces831D Office Keys

    D. Office Keys time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  9. Linux安装gcc/g++

    直接使用yum安装 yum install gcc yum -y install gcc-c++ 如果为RedHat  yum需要注册 可以参考更换yum源 https://www.cnblogs.c ...

  10. python的无限循环及退出

    题目要求如下: 1 循环验证用户输入的用户名与密码 2 认证通过后,运行用户重复执行命令 3 当用户输入命令为quit时,则退出整个程序  代码如下 person={'name':'Helen','p ...