前言

上一篇中我们学习了行为型模式的备忘录模式(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设计模式之十三 ---- 观察者模式和空对象模式的更多相关文章

  1. Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式

    前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...

  2. Java设计模式(十三) 别人再问你设计模式,叫他看这篇文章

    原创文章,转载请务注明出处 OOP三大基本特性 封装 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类操作,对不可信的进行信息隐藏. 继承 继承是指这样一种能力,它可以使 ...

  3. Java设计模式百例 - 观察者模式

    观察者(Observer)模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,主体对象的状态变化会通知所有观察者对象.观察者模式又叫做发布-订阅(Publish/Subscribe ...

  4. 被遗忘的设计模式——空对象模式(Null Object Pattern)

    GoF(四人帮)那本<设计模式 可复用面向对象软件的基础>可谓是设计模式方面的经典之作,其中介绍的23种设计模式, 也可谓是经典中的经典.但是,设计模式的种类绝不仅仅是这23种,除此之外还 ...

  5. 设计模式 ( 十六 ) 观察者模式Observer(对象行为型)

    设计模式 ( 十六 ) 观察者模式Observer(对象行为型) 1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来 ...

  6. 设计模式:空对象模式(Null Object Pattern)

    设计模式:空对象模式(Null Object Pattern) 背景 群里聊到<ASP.NET设计模式>,这本书里有一个“Null Object Pattern”,大家就闲聊了一下这个模式 ...

  7. Java设计模式之《观察者模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...

  8. C# 设计模式之空对象模式

    最近看了不少的书籍和视频等相关资料,决定自己边学习边写一下个人对设计模式的理解,如果有不对的请大家多多指正. 今天先说说我个人觉得最简单的设计模式 -- [空对象模式] 空对象模式可以减少客户端对对象 ...

  9. GoLang设计模式12 - 空对象模式

    空对象设计模式是一种行为型设计模式,主要用于应对空对象的检查.使用这种设计模式可以避免对空对象进行检查.也就是说,在这种模式下,使用空对象不会造成异常. 空对象模式的组件包括: Entity:接口,定 ...

随机推荐

  1. SQL Case when 的使用

    Case具有两种格式.简单Case函数和Case搜索函数. --简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END ...

  2. java使用java.lang.management监视和管理 Java 虚拟机

    ClassLoadingMXBean 用于 Java 虚拟机的类加载系统的管理接口. CompilationMXBean 用于 Java 虚拟机的编译系统的管理接口. GarbageCollector ...

  3. MySQL:对于几个测试题的详细研究

    最近在做MySQL作业时候遇到了很多问题,MySQL作业链接:https://www.cnblogs.com/wj-1314/p/9213885.html 所以下面谈一下稍微难一点的数据库问题,我们需 ...

  4. U3D MonoBehaviour

    一.简介 MonoBehaviour是每个脚本派生类的基类,它定义了一个脚本文件从最初被加载到最终被销毁的一个完整过程. 这个过程通过对应的方法体现出来,在不同的方法完成不同的功能,我们把这些方法称为 ...

  5. 数据库部分(MySql)_1

    SQL规范 以 “ ; ” 结尾:关键字之间要有空格(可以由多个空格):SQL语句中可以一个或多个换行:关键字不区分大小写. 数据库相关SQL 查询所有数库库: show databases; 创建数 ...

  6. NLog基础配置

    <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nl ...

  7. mysql查看执行sql语句的记录日志

    开启日志模式 -- 1.设置 -- SET GLOBAL log_output = 'TABLE';SET GLOBAL general_log = 'ON';  //日志开启 -- SET GLOB ...

  8. SQL SERVER 如何判断是不是年,月最后一天

    , SYSDATETIME())) PRINT '不是'; ELSE PRINT '是'; GO , SYSDATETIME())) PRINT '不是'; ELSE PRINT '是'; GO 1. ...

  9. hive命令的三种执行方式

    hive命令的3种调用方式 方式1:hive –f  /root/shell/hive-script.sql(适合多语句) hive-script.sql类似于script一样,直接写查询命令就行 不 ...

  10. 【Java深入研究】6、CGLib动态代理机制详解

    一.首先说一下JDK中的动态代理: JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的 但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该 ...