观望者格局,Java进级篇设计形式之十三

2019-10-05 07:07 来源:未知

简介

  • 前言

    • 定义:观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新,此种模式通常被用来实现事件处理系统
    • 别称:发布-订阅(Subscribe)模式
    • 别称:模型-视图模式
  • UML类图

    图片 1观察者模式

    • Subject:抽象主题,抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象
    • ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知
    • Observer:抽象观察者,它定义了一个更新接口,使得在得到主题更改通知时更新自己
    • ConcrereObserver:具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
  • 一个栗子

  • 场景:微信公众号,用户,公众号

  • 抽象观察者public interface Observer {public void update(String message);}

  • 具体观察者(ConcrereObserver)public class WechatUser implements Observer {// 微信用户名private String name;public WechatUser(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + "-" + message);}}

  • 抽象被观察者public interface Subject {/*** 增加订阅者/public void attach(Observer observer);/** 删除订阅者/public void detach(Observer observer);/** 通知订阅者更新消息*/public void notify(String message);}

  • 具体被观察者(ConcreteSubject)public class SubscriptionSubject implements Subject {// 储存订阅公众号的微信用户private List<Observer> wechatUserlist = new ArrayList<Observer>();

     @Override public void attach(Observer observer) { wechatUserlist.add; } @Override public void detach(Observer observer) { wechatUserlist.remove; } @Override public void notify(String message) { for (Observer observer : wechatUserlist) { observer.update; } }}
    
  • 客户端调用public class Client {public static void main(String[] args) {SubscriptionSubject mSubject = new SubscriptionSubject();// 创建微信用户WechatUser user1 = new WechatUser;WechatUser user2 = new WechatUser;WechatUser user3 = new WechatUser;// 订阅公众号mSubject.attach;mSubject.attach;mSubject.attach;// 公众号更新发出消息给订阅的微信用户mSubject.notify("公众号更新消息");}}

  • 输出结果张三-公众号更新消息李四-公众号更新消息王五-公众号更新消息

  • 使用场景及优缺点

    • 使用场景
    • 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
    • 事件多级触发场景。
    • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
    • 优点解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
    • 缺点在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现
  • Android 当中的应用

    • OnClickListener
    • ContentObserver
    • android.database.Observable
    • 还有组件通讯库RxJava、RxAndroid
    • EventBus
    • 经典:Adapter 的 notifyDataSetChanged()

观察者模式又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。。 其主要目的是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式主要由这四个角色组成,抽象主题角色、具体主题角色(ConcreteSubject)、抽象观察者角色和具体观察者角色(ConcreteObserver)。

抽象主题角色:它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

具体主题角色(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

抽象观察者角色:主要是负责从备忘录对象中恢复对象的状态。

示例图如下:

图片 2

我们这里用一个示例来进行说明吧。 我们在视频网站进行看剧追番的时候,一般会有一个订阅功能,如果对某个番剧点了订阅,那么该番剧在更新的时候会向订阅该番剧的用户推送已经更新的消息,如果取消了订阅或者没有订阅,那么用户便不会收到该消息。 那么我们可以根据这个场景来使用备忘录模式来进行开发。

首先定义一个抽象主题, 将观察者聚集起来,可以进行新增、删除和通知,这里就可以当做番剧。 代码如下:

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;

}

@Override

public void callOff(UserObserver user) {

if(!list.isEmpty

System.out.println("用户"+user.getName()+"取消订阅"+anime+"!");

list.remove;

}

@Override

public void notifyUser() {

System.out.println(anime+"更新了!开始通知订阅该番剧的用户!");

list.forEach(user->

user.update

);

}

}

最后再定义了一个具体观察者,实现抽象观察者(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";

Stringbingguo = "冰菓";

Stringfate = "fate/zero";

BangumiSubject bs1 = new Bangumi;

BangumiSubject bs2 = new Bangumi;

UserObserver uo1 = new User;

UserObserver uo2 = new User;

//进行订阅

bs1.toThem;

bs1.toThem;

bs2.toThem;

bs2.toThem;

//进行通知

bs1.notifyUser();

bs2.notifyUser();

//取消订阅

bs1.callOff;

bs2.callOff;

//进行通知

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 {

return new RealUser;

}

}

return new NullUser();

}

}

最后再来进行测试,测试代码如下:

public static void main(String[] args) {

AbstractUser au1 = UserFactory.getUser;

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进阶架构学习交流:952124565

本群提供免费的学习指导 架构资料 以及解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

TAG标签:
版权声明:本文由金沙澳门唯一官网发布于编程教学,转载请注明出处:观望者格局,Java进级篇设计形式之十三