Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式
前言
在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern)。本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer Pattern)和空对象模式模式(NullObject Pattern)。
观察者模式
简介
观察者模式又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。。
其主要目的是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式主要由这四个角色组成,抽象主题角色(Subject)、具体主题角色(ConcreteSubject)、抽象观察者角色(Observer)和具体观察者角色(ConcreteObserver)。
- 抽象主题角色(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题角色(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
- 抽象观察者角色(Observer):主要是负责从备忘录对象中恢复对象的状态。
示例图如下:

我们这里用一个示例来进行说明吧。
我们在视频网站进行看剧追番的时候,一般会有一个订阅功能,如果对某个番剧点了订阅,那么该番剧在更新的时候会向订阅该番剧的用户推送已经更新的消息,如果取消了订阅或者没有订阅,那么用户便不会收到该消息。
那么我们可以根据这个场景来使用备忘录模式来进行开发。
首先定义一个抽象主题, 将观察者(订阅者)聚集起来,可以进行新增、删除和通知,这里就可以当做番剧。
代码如下:
interface BangumiSubject{
void toThem(UserObserver user);
void callOff(UserObserver user);
void notifyUser();
}
然后再定义一个抽象观察者,有一个主要的方法update,主要是在得到通知时进行更新,这里就可以当做是用户。
代码如下:
interface UserObserver{
void update(String bangumi);
String getName();
}
然后再定义一个具体主题,实现了抽象主题(BangumiSubject)接口的方法,同时通过一个List集合保存观察者的信息,当需要通知观察者的时候,遍历通知即可。
代码如下:
class Bangumi implements BangumiSubject {
private List<UserObserver> list;
private String anime;
public Bangumi(String anime) {
this.anime = anime;
list = new ArrayList<UserObserver>();
}
@Override
public void toThem(UserObserver user) {
System.out.println("用户"+user.getName()+"订阅了"+anime+"!");
list.add(user);
}
@Override
public void callOff(UserObserver user) {
if(!list.isEmpty())
System.out.println("用户"+user.getName()+"取消订阅"+anime+"!");
list.remove(user);
}
@Override
public void notifyUser() {
System.out.println(anime+"更新了!开始通知订阅该番剧的用户!");
list.forEach(user->
user.update(anime)
);
}
}
最后再定义了一个具体观察者,实现抽象观察者(UserObserver)接口的方法。
代码如下:
class User implements UserObserver{
private String name;
public User(String name){
this.name = name;
}
@Override
public void update(String bangumi) {
System.out.println(name+"订阅的番剧: " + bangumi+"更新啦!");
}
@Override
public String getName() {
return name;
}
}
编写好之后,那么我们来进行测试。
这里我们定义两个用户角色,张三和xuwujing,他们都订阅了<冰菓>和<fate/zero>番剧,当番剧更新的时候,他们就会收到通知。 如果他们取消了该番剧的订阅,那么他就不会收到该番剧的通知了。
相应的测试代码如下:
public static void main(String[] args) {
String name1 ="张三";
String name2 ="xuwujing";
String bingguo = "冰菓";
String fate = "fate/zero";
BangumiSubject bs1 = new Bangumi(bingguo);
BangumiSubject bs2 = new Bangumi(fate);
UserObserver uo1 = new User(name1);
UserObserver uo2 = new User(name2);
//进行订阅
bs1.toThem(uo1);
bs1.toThem(uo2);
bs2.toThem(uo1);
bs2.toThem(uo2);
//进行通知
bs1.notifyUser();
bs2.notifyUser();
//取消订阅
bs1.callOff(uo1);
bs2.callOff(uo2);
//进行通知
bs1.notifyUser();
bs2.notifyUser();
}
输出结果:
用户张三订阅了冰菓!
用户xuwujing订阅了冰菓!
用户张三订阅了fate/zero!
用户xuwujing订阅了fate/zero!
冰菓更新了!开始通知订阅该番剧的用户!
张三订阅的番剧: 冰菓更新啦!
xuwujing订阅的番剧: 冰菓更新啦!
fate/zero更新了!开始通知订阅该番剧的用户!
张三订阅的番剧: fate/zero更新啦!
xuwujing订阅的番剧: fate/zero更新啦!
用户张三取消订阅冰菓!
用户xuwujing取消订阅fate/zero!
冰菓更新了!开始通知订阅该番剧的用户!
xuwujing订阅的番剧: 冰菓更新啦!
fate/zero更新了!开始通知订阅该番剧的用户!
张三订阅的番剧: fate/zero更新啦!
观察者模式优点:
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
观察者模式缺点
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
需要关联行为的场景;
事件需要创建一个触发链的场景,比如监控;
跨系统的消息交换场景,比如消息队列、事件总线的处理机制。
注意事项:
如果顺序执行,某一观察者错误会导致系统卡壳,建议采用异步方式。
空对象模式
简介
空对象模式(NullObject Pattern)主要是通过一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。 这样的Null 对象也可以在数据不可用的时候提供默认的行为。
其主要目的是在进行调用是不返回Null,而是返回一个空对象,防止空指针异常。
空对象模式,作为一种被基本遗忘的设计模式,但却有着不能被遗忘的作用。为什么说这么说呢,因为这种模式几乎难以见到和使用,不是它不够好用,也不是使用场景少 ,而是相比于简单的空值判断,使用它会显得比较复杂,至于为什么这么说,我们可以通过以下示例来进行说明。
假如我们要根据用户在已存的数据中进行查找相关信息,并且将它的信息给返回回来的话,那么一般我们是通过该用户的名称在数据库中进行查找,然后将数据返回,但是在数据库中进行查找时,很有可能没有该用户的信息,因此返回Null,如果稍不注意,就会出现空指针异常。这时我们一般的做法是,查询之后判断该数据是否为Null,如果为Null,就告知客户端没有这条数据,虽然这么做可以防止空指针异常,但是类似该方法过多,并且返回的信息实体为同一个的时候,我们每次都需要判断,就有点过于繁琐。那么这时我们就可以使用空对象模式来实现这方面的功能。
首先定义一个抽象角色,有获取姓名和判断是否为空的方法,这个抽象类的代码如下:
interface AbstractUser {
String getName();
boolean isNull();
}
定义好该抽象类之后,我们再来定义具体实现类。这里定义两实现个类,一个表示是真实的用户,返回真实的姓名,一个是不存在的用户,用另一种方式返回数据,可以告知客户端该用户不存在,预防空指针。
代码如下:
class RealUser implements AbstractUser {
private String name;
public RealUser(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isNull() {
return false;
}
}
class NullUser implements AbstractUser {
@Override
public String getName() {
return "user is not exist";
}
@Override
public boolean isNull() {
return true;
}
}
然后在来定义一个工厂角色,用于对客户端提供一个接口,返回查询信息。
代码如下:
class UserFactory {
public static final String[] names = { "zhangsan", "lisi", "xuwujing" };
public static AbstractUser getUser(String name) {
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(name)) {
return new RealUser(name);
}
}
return new NullUser();
}
}
最后再来进行测试,测试代码如下:
public static void main(String[] args) {
AbstractUser au1 = UserFactory.getUser("wangwu");
AbstractUser au2 = UserFactory.getUser("xuwujing");
System.out.println(au1.isNull());
System.out.println(au1.getName());
System.out.println(au2.isNull());
System.out.println(au2.getName());
}
输出结果:
true
user is not exist
false
xuwujing
空对象优点:
可以加强系统的稳固性,能有效防止空指针报错对整个系统的影响;
不依赖客户端便可以保证系统的稳定性;
空对象缺点:
需要编写较多的代码来实现空值的判断,从某种方面来说不划算;
使用场景:
需要大量对空值进行判断的时候;
其它
音乐推荐
分享一首很有节奏感的电音!
项目的代码
java-study是本人在学习Java过程中记录的一些代码,也包括之前博文中使用的代码。如果感觉不错,希望顺手给个start,当然如果有不足,也希望提出。
github地址: https://github.com/xuwujing/java-study
原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!
版权声明:
作者:虚无境
博客园出处:http://www.cnblogs.com/xuwujing
CSDN出处:http://blog.csdn.net/qazwsxpcm
个人博客出处:http://www.panchengming.com
Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式的更多相关文章
- Java设计模式之十三 ---- 观察者模式和空对象模式
前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...
- Java进阶篇 设计模式之十四 ----- 总结篇
前言 本篇是讲述之前学习设计模式的一个总结篇,其目的是为了对这些设计模式的进行一个提炼总结,能够通过查看看此篇就可以理解一些设计模式的核心思想. 设计模式简介 什么是设计模式 设计模式是一套被反复使用 ...
- Java进阶篇设计模式之一 ----- 单例模式
前言 在刚学编程没多久就听说过设计模式的大名,不过由于当时还是个彻彻底底的菜鸟,并没有去触碰.直到在开始工作中对简单的业务代码较为熟悉之后,才正式的接触设计模式.当时最早接触的设计模式是工厂模式,不过 ...
- Java进阶篇设计模式之十 ---- 访问者模式和中介者模式
前言 在上一篇中我们学习了行为型模式的解释器模式(Interpreter Pattern)和迭代器模式(Iterator Pattern).本篇则来学习下行为型模式的两个模式,访问者模式(Visito ...
- Java进阶篇设计模式之七 ----- 享元模式和代理模式
前言 在上一篇中我们学习了结构型模式的组合模式和过滤器模式.本篇则来学习下结构型模式最后的两个模式, 享元模式和代理模式. 享元模式 简介 享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能 ...
- Java进阶篇设计模式之十一 ---- 策略模式和模板方法模式
前言 在上一篇中我们学习了行为型模式的访问者模式(Visitor Pattern)和中介者模式(Mediator Pattern).本篇则来学习下行为型模式的两个模式,策略模式(Strategy Pa ...
- Java进阶篇设计模式之九----- 解释器模式和迭代器模式
前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解 ...
- Java进阶篇设计模式之八 ----- 责任链模式和命令模式
前言 在上一篇中我们学习了结构型模式的享元模式和代理模式.本篇则来学习下行为型模式的两个模式, 责任链模式(Chain of Responsibility Pattern)和命令模式(Command ...
- Java进阶篇设计模式之二 ----- 工厂模式
前言 在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模 ...
随机推荐
- vim编辑器常见命令归纳大全
Esc:命令行模式 i:插入命令 a:附加命令 o:打开命令 c:修改命令 r:取代命令 s:替换命令 以上进入文本输入模式 : 进入末行模式 末行模式: w:保存 q:退出,没保存则无法退出 w ...
- 循环中else的用法
name = 'hello' for x in name: print(x) if x == 'l': break #退出for循环 else: print("==for循环过程中,如果没有 ...
- 【ASP.NET Core】JSON Patch 使用简述
JSON Patch 是啥玩意儿?不知道,直接翻译吧,就叫它“Json 补丁”吧.干吗用的呢?当然是用来修改 JSON 文档的了.那咋修改呢?比较常见有四大操作:AMRR. 咋解释呢? A—— Add ...
- python_code list_3
>>> seq=['foo','x41','?','***']>>> def func(x): return x.isalnum() >>> li ...
- MySQL varchar类型数据转tinyint类型
在mysql数据库中性别字段以前存的是'男'和'女',使用varchar类型存储的,但是在我mongo库中这个字段使用的是'1'和'0'存储的,在两个库之间的数据转换就很不方便,于是想要统一存储类型, ...
- vim折叠快捷键
参考:http://www.cnblogs.com/fakis/archive/2011/04/14/2016213.html 1. 折叠方式 可用选项来设定折叠方式: 可在Vim 配置文件中设置 s ...
- SSM-SpringMVC-19:SpringMVC中请求和响应的乱码解决
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 配置一道拦截器即可解决乱码 配置方式如下: 在web.xml中: <!--过滤器处理乱码--> ...
- 推荐一个比crontab更好用的东西:crongo
This is a crontab service that supports hot plug and high performance. In addition, it supports seco ...
- python类型转换convert实例分析
在python的开发过程中,难免会遇到类型转换,这里给出常见的类型转换demo: 类型 说明 int(x [,base ]) 将x转换为一个整数 long(x [,base ]) 将x转换为一个长整数 ...
- selenium测试(Java)-- 显式等待(九)
转自:https://www.cnblogs.com/moonpool/p/5668571.html 显式等待可以使用selenium预置的判断方法,也可以使用自定义的方法. package com. ...