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-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

原文地址:https://www.cnblogs.com/xuwujing/p/10036204.html

时间: 2024-08-07 15:35:15

Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式的相关文章

Java进阶篇 设计模式之十四 ----- 总结篇

前言 本篇是讲述之前学习设计模式的一个总结篇,其目的是为了对这些设计模式的进行一个提炼总结,能够通过查看看此篇就可以理解一些设计模式的核心思想. 设计模式简介 什么是设计模式 设计模式是一套被反复使用的.多数人知晓的.经过分类编目的.代码设计经验的总结. 为什么使用设计模式 使用设计模式是为了重用代码.让代码更容易被他人理解.保证代码可靠性. 设计模式类型 设计模式有23种类型.按照主要分类可以分为三大类: 一.创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 n

Java进阶篇设计模式之一 ----- 单例模式

前言 在刚学编程没多久就听说过设计模式的大名,不过由于当时还是个彻彻底底的菜鸟,并没有去触碰.直到在开始工作中对简单的业务代码较为熟悉之后,才正式的接触设计模式.当时最早接触的设计模式是工厂模式,不过本文讲的是单例模式,这里就留着下篇文章中在讲解.至于为什么先讲解单例模式? 那是因为单例模式是设计模式中最简单的... .凡事总有个先后顺序,所以就先易后难了.好了,废话不多说了,开始进入正片. 设计模式简介 说明:这里说了的简介就是真的 "简介". 什么是设计模式 设计模式是一套被反复使

Java进阶篇设计模式之七 ----- 享元模式和代理模式

前言 在上一篇中我们学习了结构型模式的组合模式和过滤器模式.本篇则来学习下结构型模式最后的两个模式, 享元模式和代理模式. 享元模式 简介 享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 用通俗的话来说就是进行共用.生活中也有一些例子,比如之前很火的共享单车,更早之前的图书馆,编程中经常用的String类,数据库连接池等等.当然,享元模式主要的目的是复用,如果该对象没有的话,就会进行创建. 享

Java进阶篇设计模式之八 ----- 责任链模式和命令模式

前言 在上一篇中我们学习了结构型模式的享元模式和代理模式.本篇则来学习下行为型模式的两个模式, 责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern). 责任链模式 简介 责任链模式顾名思义,就是为请求创建了一个接收者对象的链.这种模式给予请求的类型,对请求的发送者和接收者进行解耦.这种类型的设计模式属于行为型模式.在这种模式中,通常每个接收者都包含对另一个接收者的引用.如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接

Java进阶篇设计模式之九----- 解释器模式和迭代器模式

前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解释器模式(Interpreter Pattern)和迭代器模式(Iterator Pattern). 解释器模式 简介 解释器模式顾名思义,就是对某事物进行解释.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 解释器模式

Java进阶篇设计模式之四 -----适配器模式和桥接模式

前言 在上一篇中我们学习了创建型模式的建造者模式和原型模式.本篇则来学习下结构型模式的适配器模式和桥接模式. 适配器模式 简介 适配器模式是作为两个不兼容的接口之间的桥梁.这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能. 简单的来说就是通过某个接口将不兼容的两个类进行兼容,俗称转换器. 生活比较典型的例子是电器的电压,美国的电压是110V左右, 而中国的电压普片是220V,如果我们想用美国或日本的电器,则需要一个转换器,将110V转换成220V.还有一个很典型例子就是曾经的万能充,

Java进阶篇设计模式之五-----外观模式和装饰器模式

前言 在上一篇中我们学习了结构型模式的适配器模式和桥接模式.本篇则来学习下结构型模式的外观模式和装饰器模式. 外观模式 简介 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口.这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性. 简单的来说就是对外提供一个简单接口,隐藏实现的逻辑.比如常用电脑的电源键,我们只需按电源键,就可以让它启动或者关闭,无需知道它是怎么启动的(启动CPU.启动内存.启动硬盘),怎么关闭的(关闭硬盘.关闭内存.关闭CPU)

Java进阶篇设计模式之六 ----- 组合模式和过滤器模式

前言 在上一篇中我们学习了结构型模式的外观模式和装饰器模式.本篇则来学习下组合模式和过滤器模式. 组合模式 简介 组合模式是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组合对象,用来表示部分以及整体层次.这种类型的设计模式属于结构型模式,它创建了对象组的树形结构. 简单来说的话,就是根据树形结构把相似的对象进行组合,然后表示该部分是用来做啥的.在中有个很形象的例子,就是电脑中的 文件系统. 文件系统由目录和文件组成.每个目录都可以装内容.目录的内容可以是文件,也可以是目录.按照

设计模式(十三): Proxy代理模式 -- 结构型模式

  设计模式(十一)代理模式Proxy(结构型) 1.概述 因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路径) ? 你有想过限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户?以上两种需求都非常类似,并且都需要解决一个更大的问题:你如何提供一致的接口给某个对象让它可以改变其内部功能,或者是从来不存在的功能? 可以通过引入一个新的对象,来实现对真实对象的操作或者将新的对象作为真实对象