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,他们都订阅了<冰菓>和番剧,当番剧更新的时候,他们就会收到通知。 如果他们取消了该番剧的订阅,那么他就不会收到该番剧的通知了。
相应的测试代码如下:
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设计模式之十三 ---- 观察者模式和空对象模式的更多相关文章
- Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式
前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...
- Java设计模式(十三) 别人再问你设计模式,叫他看这篇文章
原创文章,转载请务注明出处 OOP三大基本特性 封装 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类操作,对不可信的进行信息隐藏. 继承 继承是指这样一种能力,它可以使 ...
- Java设计模式百例 - 观察者模式
观察者(Observer)模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,主体对象的状态变化会通知所有观察者对象.观察者模式又叫做发布-订阅(Publish/Subscribe ...
- 被遗忘的设计模式——空对象模式(Null Object Pattern)
GoF(四人帮)那本<设计模式 可复用面向对象软件的基础>可谓是设计模式方面的经典之作,其中介绍的23种设计模式, 也可谓是经典中的经典.但是,设计模式的种类绝不仅仅是这23种,除此之外还 ...
- 设计模式 ( 十六 ) 观察者模式Observer(对象行为型)
设计模式 ( 十六 ) 观察者模式Observer(对象行为型) 1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来 ...
- 设计模式:空对象模式(Null Object Pattern)
设计模式:空对象模式(Null Object Pattern) 背景 群里聊到<ASP.NET设计模式>,这本书里有一个“Null Object Pattern”,大家就闲聊了一下这个模式 ...
- Java设计模式之《观察者模式》及应用场景
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...
- C# 设计模式之空对象模式
最近看了不少的书籍和视频等相关资料,决定自己边学习边写一下个人对设计模式的理解,如果有不对的请大家多多指正. 今天先说说我个人觉得最简单的设计模式 -- [空对象模式] 空对象模式可以减少客户端对对象 ...
- GoLang设计模式12 - 空对象模式
空对象设计模式是一种行为型设计模式,主要用于应对空对象的检查.使用这种设计模式可以避免对空对象进行检查.也就是说,在这种模式下,使用空对象不会造成异常. 空对象模式的组件包括: Entity:接口,定 ...
随机推荐
- SQL 必知必会·笔记<12>组合查询
什么是组合查询 SQL 通过执行多个查询(多条SELECT 语句),并将结果作为一个查询结果集返回.这些组合查询通常称为并(union)或复合查询(compound query). 什么时候使用组合查 ...
- leetcode — palindrome-number
import org.lep.leetcode.parseint.IntegerParser; /** * Source : https://oj.leetcode.com/problems/pali ...
- 反调试手法之CreateProcess反调试
反调试手法之CreateProcess反调试 在学习Win32 创建进程的时候.我们发现了有一个进程信息结构体. STARTUPINFO. 这个结构体可以实现反调试. 具体CreateProcess可 ...
- Linux下lz4解压缩命令小结
lz4是一个让"人见人爱.花见花开"的压缩算法,能够在多核上很好的扩展.lz4在压缩率上略微逊色, 但是在解压速度上有着惊人的优势 (大概是gzip的3倍(多次测试对比)).因为压 ...
- mybatis教程4(动态SQL)
动态SQL语句 MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦.例如拼接时要确保不能忘记添加必要的空 ...
- netty源码解解析(4.0)-7 线程模型-IO线程EventLoopGroup和NIO实现(二)
把NIO事件转换成对channel unsafe的调用或NioTask的调用 processSelectedKeys()方法是处理NIO事件的入口: private void processSelec ...
- 正则表达式,re模块
一,正则表达式 正则表达式是对字符串操作的一种逻辑公式,我们一般使用正则表达式对字符串进行匹配和过滤,使用正则的优缺点,我们可以去http://tool.chinaz.com/regex/进行测试. ...
- override与new的区别
using System; namespace ConsoleAppDemo { class BaseClass { public void Fun() { Console.WriteLine(&qu ...
- Java基础——Oracle(四)
一.Sql * plus 常用命令 1.关于登录,连接的几个命令 1) conn[nect] //例 conn system/manager 用法 conn 用户名/密码 @网络服务名 (as sy ...
- 【Dubbo&&Zookeeper】2、 windows平台dubbo-admin管理平台搭建
一.前言 dubbo的使用,其实只需要有注册中心,消费者,提供者这三个就可以使用了,但是并不能看到有哪些消费者和提供者,为了更好的调试,发现问题,解决问题,因此引入dubbo-admin.通过dubb ...