设计模式(二)观察者(Observer)模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21019419/article/details/83015979

参考:

  1. 《headfirst设计模式》
  2. https://blog.csdn.net/hguisu/article/details/7556625
  3. https://www.cnblogs.com/luohanguo/p/7825656.html
  4. 解析:https://www.cnblogs.com/V1haoge/p/6513651.html
  5. 应用:https://www.aliyun.com/jiaocheng/291734.html
  6. 应用:https://blog.csdn.net/qq_36523667/article/details/79642860
  7. 应用和解析:https://blog.csdn.net/h330531987/article/details/77819184
  8. 应用:https://www.cnblogs.com/zishengY/p/7056948.html

大家都看看我贴出来的参考地址,有一些文章对观察者模式解释的很好了,另外的一些是实际的一些应用

这篇文章其实主要是读书笔记

概念解析

观察者模式是jdk中使用最多的模式之一,非常有用(mvc,springboot等都有大量的应用这个模式思路)。观测模式定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

话不多说看例子解析
通过报纸和杂志的订阅,这种关系来解释一下观察者模式。

  • 报纸的业务是出版报纸 。
  • 用户向报社订阅报纸,那么只要报社有新的报纸就会送到用户手上。
  • 如果用户不想要报纸了,只需要取消订阅即可。
  • 但是报纸还是会向那些订阅了的用户发送报纸。

很熟悉的味道,现在的一些广告邮件就是这么做的嘛。我在steam上订阅了一些游戏推荐,不定期的steam会推送一些邮件给我,后面我觉得太多了,也不怎么玩游戏了,就取消了订阅,之后就安静了。

以查看帖子topic为例,加载帖子loadTopicById(String id),当用户点击打开topic的时候,一般的编程方式在后台处理很多的任务,比如topic的阅读量+1,发帖人的积分+1,当前用户的阅读记录增加,等等操作。如果把这些+1+2+n的操作都放到loadTopicByID(String id)这个里面,就显得非常臃肿,耗时,并且如果你在增加积分的时候出了bug那可能就影响了整体数据,甚至可能返回错误信息,其实它只想要topic而已啊,那么这些与实际想要的内容基本无关的操作,是否可以独立出来?(oo设计模式中的,变化和不变的分离)而且,万一以后产品经理又加了个什么鬼逻辑,比如赠送优惠券(鬼知道为何查看topic要赠送优惠券)?那还是要修改整个loadTopicById方法,维护就会变得越来越复杂了。
这个时候,观察者模式就能排上用场了,支线任务和主任务分离。
当然最终的实现方式有很多种,可以用线程(异步省时),可以用队列,或者直接新建class单独处理,报错了也不影响主要业务。

据此我们得到了一个模式:出版者 + 订阅者 = 观察者模式,当然名称可能需要更改一下,出版者为主题(Subject),订阅者为观察者(Observer)。
看图
在这里插入图片描述

应用场景1

大部分人还是关注怎么用吧,或者可能的场景是如何的

  1. 当一个抽象模型有2个方面,其中一方面依赖于另外一方面,将这2者封装在独立的对象中,以使它们可以各自独立的改变和复用。
  2. 当对一个对象的改变,需要同时更改其他对象,而不知道具体有多少额外的对象有待改变时。
  3. 当一个对象必须通知其他对象,而它又不能假定其他对象是谁时。

说实话,看着一堆文字解释的时候,眼睛都是晕的,尤其是下午没有睡午觉的时候。

模式组成

角色组成

  1. 主题Subject(被观察对象,报社):主题知道它的观察者(订阅的读者),可以有任意多个观察者观察同一个主题。提供注册(registerObserver())和删除(removeObserver())观察者对象的接口。通常使用接口的方式实现观察者模式,当然也有其他的实现方式。
  2. 具体主题(ConcreteSubject):一个具体主题总是实现主题接口,注册和删除方法之外,还实现了通知方法(notifyObservers()),主要是用于当状态改变时,更新所有的观察者。
  3. 观察者(Observer):定义为一个接口,当主题改变时,此接口的更新方法被调用。
  4. 具体观察者(ConcreteObserver):实现了观察者接口的任意类。观察者必须注册具体主题(维护一个指向ConcreteSubject对象的引用),用来接收更新消息。

看个图吧
在这里插入图片描述

以下大部分摘抄自《headfirst设计模式》一书。

关于观察者的一切,主题只知道观察者实现了某个接口(Observer)。主题不需要知道观察者的具体类是谁,做了什么或者其他的细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表。所以我们可以随时增加观察者,那么我们也可以在运行时用心的观察者取代旧的观察者,但是主题不会受到影响。同时也可以删除。
有新类型的观察者出现,主题的代码不需要修改。主题不在乎新的观察者是如何实现的,只要他实现了Observer接口,并且注册了,那么主题就会向他发送消息。
改变主题或者观察者其中一方,并不会影响另一方。两者是松耦合

代码实现

说了辣么多,还不如用代码实现看看,最直接。
既然是面向接口编程,所有的第一步应该是编写接口,创建一个主题接口,也可以叫被观察者,或者报社。这个报社有3个主要的方法,一个是提供订阅的方法(regist),一个是删除订阅(remove),另外一个是通知订阅者(notify)。注册观察者的时候,肯定要告诉被观察者是谁来注册的,所以要带上osbserver对象。

public interface Subject {
    /**
     * 注册, 传入观察者变量,注册信息
     */
    void registerObserver(Observer o);
    /**
     * 删除
     */
    void removeObserver(Observer o);
    /**
     * 通知, 主题改变时,调用此方法,通知所有的观察者.
     */
    void notifyObserver();
}

主题有了,下面就是观察者,也就是读者的接口。观察者自己怎么处理数据其实我们(报社)不关心,有的读者拿到了报纸喜欢去厕所读,有的拿到了报纸喜欢用来做折纸工艺品,等等。但是who care,我们只需要负责把报纸发送到他手上就可以了,所以接口只需要有一个update(String temp)发送数据(报纸)给他,当然也要顺便告诉他变更的是什么内容。

public interface Observer {
    /**
     * 更新数据, 传入需要变更的数据.
     */
    void update(String temp);
}

好了,有了接口了,就开始对接口进行编程了。需要变更的数据,或者状态,都是在主题这边维护。

public class WeatherData implements Subject {

    /**
     * 会变更的数据.
     */
    private String temp;

    public void setTemp(String temp) {
        this.temp = temp;
        notifyObserver();
    }

    /**
     * 注册的观察者.
     */
    private List obs;

    public WeatherData(){
        obs = new ArrayList();
    }

    @Override
    public void registerObserver(Observer o) {
        obs.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        obs.remove(o);
    }

    @Override
    public void notifyObserver() {
        for (int i = 0; i < obs.size(); i++) {
            Observer o = (Observer) obs.get(i);
            o.update(temp);
        }
    }
}

新建一个类实现观察者

public class ConcreteObserver implements Observer {

    // 用于接收被改变的数据
    private String temp;

    // 指定一个主题的引用, 方便注册和删除
    private Subject sub;

    public ConcreteObserver(Subject subject) {
        sub = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update(String temp) {
        System.out.println("ConcreteObserver: 被通知数据发生了改变:" + temp);
    }
}

编写测试方法

@Test
public void test(){
    WeatherData weatherData = new WeatherData();
    ConcreteObserver o = new ConcreteObserver(weatherData);

    weatherData.setTemp("test");
    weatherData.setTemp("test222");
}
控制台打印:
ConcreteObserver: 被通知数据发生了改变:test
ConcreteObserver: 被通知数据发生了改变:test222

在观察者初始化的时候,进行了注册,所以她可以收到通知。

事实上jdk中也有实现了Observer和Observable,只不过Observable是一个类,而不是接口。observable里面已经有很完善的实现注册,删除,通知的方法。所以可以直接继承。但是毕竟是一个class,还是受到一些限制的。所示这个Observable并不太符合OO的设计原则。

优缺点

优点

观察者模式可以实现表示层和数据逻辑层的分离,很熟悉的感觉,mvc有木有。以mvc模式看,表示层作为观察者,逻辑处理作为主题,我们在写完业务层之后,传递给表示层的其实只有页面的地址(return “/pages/xxx/list”;或者另外的写法:return ModelAndView(“xxx”))。
在观察目标和观察者之间建立一个抽象的耦合,主题,报社,subject所知道的其实就是他拥有一系列的观察者,每个观察者都实现了Observer接口,但是subject又不知道每个观察者的具体实现。报社知道有人订阅了,但是他不知道你是个什么样的人,它也不关心你是个什么样的人。
支持广播通信
观察者模式符合“开闭原则”的要求

缺点

如果一个subject有很多直接或者间接的观察者的话,将所有的观察者通知到会话费很多时间;
观察者和观察目标之间有循环依赖的话,会导致死循环,造成系统崩溃;
观察者模式没有相应的机制让观察者知道所观察的目标对象是如何发生变化的,只是知道它发生了改变。
错误的更新,因为一个观察者不知道其他观察者的存在,所以当他对观察目标进行修改时,可能造成其他严重的后果。在观察目标上,一个细小的改变错误,可能会影响一系列的观察者的变更,而且很难捕捉到这种变更错误。

总结

Observer模式,把一对多对象之间的依赖关系变得极为松散,提高了程序的可维护性和拓展性,也符合开闭原则。

在webapp中,怎么完美的注册观察者是个问题,我暂时没有什么好的思路,路过的大佬如果有好的方式,请指点一下。

headfirst读书分享

设计原则:

  • 为了交互对象之间的松耦合设计而努力。松耦合的设计之所以能让我们建立有弹性的oo系统,能够应对变化,是因为对象之间的相互依赖降低到了最低。
  • 封转变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间松耦合设计而努力

  1. https://blog.csdn.net/hguisu/article/details/7556625 ↩︎

猜你喜欢

转载自blog.csdn.net/qq_21019419/article/details/83015979