设计模式——行为型设计模之借助观察者模式实现模块之间的解耦

来源:互联网 发布:弹幕视频网站源码 编辑:程序博客网 时间:2024/06/05 06:38

引言

在日常生活中,我们常常会遇到多个对象共同关注某个对象的状态变化情况。比如RSS订阅、空气质量检测仪检测空气质量、温度记录仪监控温度、招聘APP中订阅对应的职位招聘信息后,及时推送到订阅者手里等等,这样的场景数不胜数,如果采取程序的思想来解决这类问题的话,观察者模式就是一个优秀的选项。

一、观察者模式概述

观察者模式(Observer Pattern)也叫做发布/订阅模型(Publish/Subscribe)是一种行为型设计模式,其官方定义——定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新(Define a one-to-many dependency between objects so that when one object changes state,all its
dependents are notified and updated automatically.)当你对一个主题对象的状态感兴趣,希望在它每次发生变化时获得通知。在观察者模式中主要有两种角色:观察另外一个对象状态的对象叫做Observer观察者;被观察的对象叫着Subject**主题对象又叫被观察者**,包含了一些需要在其状态改变时通知的观察者。

二、观察者模式的优点和缺点及可用场景

1、观察者模式的优点

  • 观察者和被观察者之间是抽象耦合,不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。

  • 可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。

  • 在主题和观察者之间建立一个抽象的耦合 ,一个主题所知道的仅仅是它拥有一系列观察者 , 每个都符合抽象的Observer类的简单接口。目标无需知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。因为目标和观察者不是紧密耦合的, 它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它 , 这样就保持了系统层次的完整。

  • 支持广播通信 ,不像通常的请求, 目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象也并不关心到底有多少对象对自己感兴趣 ,它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。

  • 观察者模式符合“开闭原则”的要求。

2、观察者模式的缺点

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
  • 意外的更新 因为一个观察者并不知道其它观察者的存在 , 它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。
  • 如果依赖准则的定义或维护不当,常常会引起错误的更新 , 这种错误通常很难捕捉。简单的更新协议不提供具体细节说明目标中什么被改变了 , 这就使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。

3、观察者模式的可用场景

  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列的处理机制。

三、观察者模式的实现

1、利用JDK自带的Observer和Observable实现观察者模式

在JDK1.2后,Java提供了对观察者模式的支持接口和实现类。其中接口 java.util.Observer 用来指定观察者,观察者必须实现 void update(Observable o, Object arg) 方法,其中参数 Observable o 用于指定触发 update 方法的对象, Object arg 用于指定触发 update 方法时候的附加参数。;而类 java.util.Observable 用来指定主题(被观察者角色),并且提供了一系列的方法(比如添加观察者、移除观察者、通知观察者等)。通过使用这个接口和实现类可以快速来实现观察者模式。

1.1、实现java.util.Observer接口实现观察者角色

package observe;import java.util.Observable;import java.util.Observer;public class UserObserver implements Observer {    private String name;    public UserObserver(String name) {        this.name=name;    }    public void update(Observable obsered, Object arg) {        LiveshowSubjector subject=(LiveshowSubjector)obsered;        System.out.println(name+"大幕大神,别划水啦"+subject.getMsg());//也可以直接从arg 获取,即所谓的拉数据形式    }    public String toString(){        return "弹幕大神:"+name;    }}

1.2、继承java.util.Observable实现主题,即被观察者角色

import java.util.Observable;/** * 主题角色,即 被观察者类 * @author cmo */public class LiveshowSubjector extends Observable {    private String msg;    public String getMsg(){        return this.msg;    }    public void pushLiveShowMsg(String msg) {        this.msg=msg;        setChanged();//Observable 自身用于标记数据发生了改变        notifyObservers(msg);//Observable 自身用于通知所有观察这个主题的观察者方法,还有其他重载形式    }}

1.3、测试JDK自带观察者模式

public class TestObservePattern {    public static void main(String[] args) {        LiveshowSubjector subject=new LiveshowSubjector();//定义主题 即被观察者        UserObserver userA=new UserObserver("有位水友");//定义观察者角色        UserObserver userB=new UserObserver("小龙人");        UserObserver userC=new UserObserver("小僵尸");        UserObserver userD=new UserObserver("小树人");        UserObserver userE=new UserObserver("小火人");        subject.addObserver(userA);//使用Observable 的方法注册 观察者        subject.addObserver(userB);        subject.addObserver(userC);        subject.addObserver(userD);        subject.addObserver(userE);        subject.pushLiveShowMsg("DOTA大神来直播啦");//当主题更新的时候,通知观察者    }}

2、自定义方式去实现观察者模式

这里写图片描述
如上图所示观察者模式的主要角色:
  

  • 抽象主题角色(即被观察者)——把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题(一般用一个抽象类和接口来实现)可以增加、删除观察者角色、通知观察者。

    • 具体主题角色——在具体主题内部状态改变时,给所有登记过的观察者发出通知。
  • 抽象观察者角色——为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。

  • 具体观察者角色——该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

1、实现抽象主题对象

主题一般需要承担注册观察者、删除观察者和通知观察者等工作。

/** *  * @author Crazy.Mo 主题,即 被观察者 */public interface ISubject<T> {    public void regist(IObserver<T> obj);// 注册观察者    public void unregist(IObserver<T> obj);// 取消注册    public void notifyObservers();// 通知观察者}

2、实现具体主题对象

由于在观察者模式中通常主题对象与观察者的关系是1:N,所以主题对象需要持有观察者的引用,而保存一组引用可以考虑采用集合。

public class Subject<T> implements ISubject<T> {    private List<IObserver<T>> observers=new ArrayList<>();    private T data;    public void setData(T data){        this.data=data;    }    @Override    public void regist(IObserver<T> obj) {        observers.add(obj);    }    @Override    public void unregist(IObserver<T> obj) {        if(observers.contains(obj)){            observers.remove(obj);        }    }    @Override    public void notifyObservers() {        for(int i=0;i<observers.size();i++){//通知所有数据观察者进行响应            observers.get(i).refresh(data);        }    }}

3、实现抽象观察者对象

观察者接收到消息后,即进行refresh方法操作,对接收到的信息进行处理

public interface IObserver<T> {    public void refresh(T data);//用于给主题调用}

4、实现具体观察者对象

public class ConcreteObserver<T> implements IObserver<T> {    private String name;    public ConcreteObserver(String name){        this.name=name;    }    @Override    public void refresh(T data) {        System.out.println(this.name+"已经接收到数据————"+data);    }}

5、测试

public class ObserverClient {    public static void main(String[] args) {        //建立主题(被观察者)和初始化观察者        Subject<String> subject=new Subject<String>();        subject.setData("主题来数据了");        IObserver<String> observer=new ConcreteObserver<String>("观察者1号");        IObserver<String> observer2=new ConcreteObserver2<String>("观察者2号");        IObserver<String> observer3=new ConcreteObserver3<String>("观察者3号");        System.out.println("创建了一个主题,并且注册了三位观察者");        //2、主题指定观察者        subject.regist(observer);        subject.regist(observer2);        subject.regist(observer3);        System.out.println("通知观察者:");        //3、主题通知观察者        subject.notifyObservers();        System.out.println("动态取消观察者2号的观察权利");        subject.unregist(observer3);        //再通知观察者        subject.notifyObservers();    }}

这里写图片描述

四、观察者模式的“推”和“拉”数据形式

1、“推”数据

推数据的方式指的是具体主题在数据变化之后全部交给具体观察者,即将变化后的状态、数据全部直接传递到具体观察者去处理,简而言之,主题对象直接将数据传递给观察者对象如前面的例子。

public interface IObserver<T> {    public void refresh(T data);//用于给主题调用}

2、“拉”数据

拉数据的方式指的是由观察者自己主动去获取主题变化后的数据,实现如下:

2.1、实现观察者角色

public interface IObserver {    public void pullData(ISubject subject);//主动拉数据}

ConcreteObserver2、ConcreteObserver3的实现略

public class ConcreteObserver implements IObserver {    private String name;    public ConcreteObserver(String name){        this.name=name;    }    @Override    public void pullData(ISubject obj) {        Subject subject=(Subject)obj;        System.out.println(this.name+"已经接收到数据(通过PULL的方式)————"+subject.getData());    }}

2.2、实现主题对象

/** *  * @author Crazy.Mo 主题,即 被观察者 */public interface ISubject {    public void regist(IObserver obj);// 注册观察者    public void unregist(IObserver obj);// 取消注册    public void notifyObservers();// 通知观察者}
public class Subject implements ISubject {    private List<IObserver> observers=new ArrayList<>();    private String data;    public void setData(String data){        this.data=data;    }    public String getData(){        return this.data;    }    @Override    public void regist(IObserver obj) {        observers.add(obj);    }    @Override    public void unregist(IObserver obj) {        if(observers.contains(obj)){            observers.remove(obj);        }    }    @Override    public void notifyObservers() {        for(int i=0;i<observers.size();i++){//观察者自己拉数据            observers.get(i).pullData(this);        }    }}

2.3、测试

public class PullClient {    /**     * @param args     */    public static void main(String[] args) {        //建立主题(被观察者)和初始化观察者        Subject subject=new Subject();        subject.setData("主题来数据了");        IObserver<String> observer=new ConcreteObserver<String>("观察者1号");        IObserver<String> observer2=new ConcreteObserver2<String>("观察者2号");        IObserver<String> observer3=new ConcreteObserver3<String>("观察者3号");        System.out.println("创建了一个主题,并且注册了三位观察者");        //2、主题指定观察者        subject.regist(observer);        subject.regist(observer2);        subject.regist(observer3);        System.out.println("通知观察者:");        //3、主题通知观察者        subject.notifyObservers();        System.out.println("动态取消观察者1号的观察权利");        subject.unregist(observer);        //再通知观察者        subject.notifyObservers();    }}

这里写图片描述

阅读全文
0 0
原创粉丝点击