观察者模式 (Observer)

1.定义

定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

2.类图

image
  • Subject

目标对象。具备功能点:1.管理多个观察者。2.提供对观察者的注册及退订。3.当状态发生改变时,对订阅有效的观察者进行通知。

  • Observer

观察者接口。定义目标通知后更新操作

  • ConcreteSubject

具体的主题,用来维护自身的状态的变化

  • ConcreteObserver

观察者具体实现对象,用来接受目标的通知,并进行后续的操作。

3.深入理解

  • 1.目标和观察者的关系按定义区分:一对多。
  • 2.单向依赖关系。只有观察者依赖与目标,目标不会依赖与观察者。

建议:

  • 目标接口定义,建议在类名称后面加上Subject
  • 观察者接口定义,建议在类名称后面加上Observer
  • 观察者接口方法,建议命名为update

4.代码

  • Subject
/**
 * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
 */
public class Subject {

    /**
     * 用于保存观察者对象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 注册观察者
     *
     * @param observer
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 删除观察者对象
     *
     * @param observer
     */
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知所有注册的观察者对象
     */
    protected void notifyObservers() {
        observers.stream().forEach(observer -> observer.update(this));
    }
}
  • Observer
/**
 * 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的后的操作
 */
public interface Observer {

    /**
     * 更新接口
     *
     * @param subject
     */
    void update(Subject subject);
}

  • ConcreteSubject
/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者
 * 并在自己状态发生改变时,通知各个观察者
 */
public class ConcreteSubject extends Subject {

    /**
     * 示意目标对象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目标对象已更新自身状态为:" + this.subjectState);
        this.notifyObservers();
    }
}
  • ConcreteObserver
/**
 * 具体的观察者对象,实现更新方法,使得自身的状态和目标状态保持一致
 */
public class ConcreteObserver implements Observer {

    /**
     * 示意,观察者对象状态
     */
    private String observerState = "呀,我是观察者初始状态!";

    @Override
    public void update(Subject subject) {
        System.out.println("观察者之前的状态为:" + this.observerState);
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("观察者接收到更新的对象状态:" + observerState);
    }
}

  • ObserverClient
/**
 * 调用示例
 */
public class ObserverClient {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer=new ConcreteObserver();
        subject.attach(observer);
        subject.setSubjectState("121");
    }
}

5. 观察者两种通知模式(推模型和拉模型)

5.1 推模型

目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或者部分数据,相当于在广播。

5.2 拉模型

目标对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。

拉模型的处理方式,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

上面的代码就是典型的拉模型。在具体的主题里,通过update方法把自身传递给具体的观察者。

5.3 如何将上面的拉模型转变为推模型

处理步骤

  • 1.主题类的批量通知方法,增加需要通知的参数
  • 2.具体观察者的update方法接受具体通知信息
  • PushSubject
public class PushSubject {
    private List<PushObserver> observers = new ArrayList<>();

    /**
     * 订阅主题
     *
     * @param observer
     */
    public void attach(PushObserver observer) {
        observers.add(observer);
    }

    /**
     * 取消订阅
     *
     * @param observer
     */
    public void detach(PushObserver observer) {
        observers.remove(observer);
    }

    /**
     * 拉模型通知所有的注册者
     */
    protected void notifyObservers(String content) {
        observers.stream().forEach(observer -> {observer.update(content);});
    }
}

  • ConcretePushSubject
public class ConcretePushSubject extends PushSubject {
    /**
     * 示意目标对象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目标对象已更新自身状态为:" + this.subjectState);
        this.notifyObservers(subjectState);
    }
}

  • PushObserver
public interface PushObserver {
    /**
     * 更新接口
     * 传入更新的内容
     * @param content
     */
    void update(String content);
}

  • ConcretePushObserver
public class ConcretePushObserver implements PushObserver {
    @Override
    public void update(String content) {
        System.out.print("主题推送的信息:" + content);
    }
}
  • Main
public static void main(String[] args) {
    ConcretePushSubject pushSubject=new ConcretePushSubject();
    ConcretePushObserver pushObserver=new ConcretePushObserver();
    pushSubject.attach(pushObserver);
    pushSubject.setSubjectState("1212");
}

5.4 如何选择拉模型与推模型

推模型是假定主题对象知道观察者需要的数据。退模型的一个缺点就是:可能会使得观察者对象难以复用,因为观察者对象的update方法是按需而定义的。ps:为了避免这样的问题,主题对象通常定义一个公用的话题。类比:微信公众号,公众号更新内容,直接推送给订阅的用户。

拉模型是主题对象无法得知观察者具体需要什么数据,干脆把自身对象传递给观察者,让观察者按需提取数据。

6.Java中的观察者

Java语言已经内置好了观察者的实现。

java.util包中定义了Observable,代表具备可以被观察的能力。也就是我们之前定义的Subject类。目标接口

java.util包中也定义了一个接口Observer,代表观察者对象接口。里面定义了update方法

6.1好处

  • 1.无须额外定义观察者和目标对象的接口,JDK已经内置。
  • 2.具体的目标实现里面无须再维护观察者的注册信息,这个在Observable类中已经定义好
  • 3.触发方式要先调用setChanged()方法
  • 4.具体的观察者的实现里,update方法能够同时支持推模型和拉模型。

6.2代码

  • 1.定义主题类
import java.util.Observable;

public class NewPaper extends Observable {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        this.setChanged();
        //this.notifyObservers();//拉模型
        this.notifyObservers(content);//推模型
    }
}
  • 2.定义观察者类
import java.util.Observable;
import java.util.Observer;

public class Reader implements Observer {

    //读者名称
    private String name;

    public Reader(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "收到报纸了,目标推送过来的内容是:" + arg);
        //System.out.println(name + "收到报纸了,主动获取到目标更新的内容:" + ((NewPaper) o).getContent());
    }
}
  • 3.客户端调用
public class Program {
    public static void main(String[] args) {
        NewPaper paper = new NewPaper();
        Reader zhangsan = new Reader("zhangsan");
        Reader lisi = new Reader("lisi");
        paper.addObserver(zhangsan);
        paper.addObserver(lisi);
        paper.setContent("iphone 8发布了!!");
    }
}

7.观察者优缺点

7.1 优点

  • 1.观察者模式实现了观察者与目标之间的抽象耦合
  • 2.观察者模式实现了动态联动
  • 3.观察者模式实现了广播通信。

7.2 缺点

  • 1.可能会引起无谓的操作

因为不管观察者是否需要,都需要调用update方法。

8.观察者的本质

观察者的本质是:触发联动

当修改目标对象的状态时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法。相当于联动调用这些观察者的方法

这个联动还是动态的,可以通过注册和取消注册来控制观察者。同事目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确的联动过来。

9.何时选用观察者

  • 1.当一个抽象模型有两个方面,一个方面依赖于另一个方面的状态改变。

  • 2.在更改一个对象时候,需要同时连带改变其他的对象,而且不知道究竟有多少对象需要被连带改变。

  • 3.当一个对象必须通知其他的对象,但是又希望这个对象和其他被它通知的对象是松散耦合的。

10.与其他设计模式的区别

10.1 观察者模式与状态模式

两者是有相似之处。但也有所不同,观察者重心是触发联动。但是到底决定哪些观察者进行联动,可以配合状态模式。

  • 观察者模式

当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作。

  • 状态模式

根据不同的状态,选择不同的实现。这个实现类的主要功能就是针对状态相应的操作。

10.2 观察者模式与中介者模式

两个模式可以一起使用。比如观察者模式里面的主题与观察者交互比较复杂,那么就可以一起使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容