美文网首页
给大妈说“观察者模式”

给大妈说“观察者模式”

作者: 番茄精 | 来源:发表于2018-01-09 18:51 被阅读0次

      说点题外话。昨天跟基友闲聊,聊到物理学家杨振宁,我才发现国内黑他,不了解他的人实在犹如路边的野草啊。我不得不给基友强调:杨振宁和霍金根本不是一个级别的! 杨振宁的学术水平和成就,放眼爱因斯坦之后,能与之比肩的寥寥无几。其中影响力最大的,是“Yang-Mills非交换规范场”理论。和李政道一起获诺贝尔的那个“宇称不守恒”也只能算杨先生的普通成就。而霍金可能科普做习惯了,动不动就是惊人之语。加上身残志坚,所以国内粉丝一大把。这不,腾讯17年的WE大会都邀请他了。但在物理大师的圈子里,可能就是韭菜水平了。
      好了,停住我达达的马蹄!开始给你扯扯这个观察者模式,同样,我希望进来的观众,就算是大妈,也能在看完之后对这个模式的理论有足够的认知,至少可以在程序猿面前装逼。下面贴上本文目录。

    • 什么是“观察者模式”
      • 定义
      • 补充说明
      • 结构
    • 怎么用“观察者模式”
      • 简单实现
      • 适用场景
    • 总结

    什么是“观察者模式”

    定义

      小时候,想看《青年文摘》、《读者》、《意林》这样的杂志要怎么做呢?大家都知道要先订阅,然后出版社每更新一期,就会发过来,只不过那时候的订阅一般走的是线下流程...现在手机上装很多应用的时候,都会弹个框问你是否允许应用发送通知,比如如果你允许手机“淘宝”APP的通知,就会时不时收到“淘宝”推送过来的消息。这就是生活中很典型的“观察者模式”。我给你ps个图,让你终生难忘,先。

    observer_pattern.png

      灰太狼光头强皮皮虾都订阅了《意林》,然后,每当出版社出了新一期的《意林》,就寄一份过来他们了。
      然后,我在提几个和这看起来像,但不是“观察者模式”的场景让你有更深刻的认识。比如,你一直关注“下周回国贾跃亭”或者股市的风吹草动,这时候,你积极地每天在浏览器搜索相关信息,这种在生活中看起来是你在“观察”,但严格来说,不是程序猿口中的“Observer Pattern”。在提一个,360红衣教主的“水滴”摄像头,其实也可以理解成是观察者模式的一个场景。
      区别在哪里?我们对比一下,不难发现关键在于“订阅”这个动作。比如,装摄像头,订阅杂志,允许APP的通知,都可以抽象成,你订阅了你要观察的事物。这时候,事物有更新,就会主动通知你。是不是很好理解?呵呵,我们再来看看权威的定义。

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

      如果有路过的大妈,看到别害怕记不住。我这就给你翻译一下:覆巢之下,焉有完卵。你这么理解,卵1号(观察者)和卵2号都十分依赖(订阅)鸟巢(被观察者),卵从出生开始,就关注(订阅)着巢的一举一动。平时风和日丽,巢没事卵也舒服。有天忽然巢被吹落掉到地上,卵们也全碎了。。。

      接下来,我们看看wiki怎么说的。

    The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

      翻译过来就是。

    观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常通过调用各观察者所提供的方法来实现。

      记不住?没关系,我相信通过上面我讲的例子,再咸的咸鱼,都能理解这个模式的含义了。这里我得加条三八线了。因为接下来的部分,大妈就不用关注了,除非你想写代码。




    补充说明

      这里开始,我可不会给你们这些抠脚大汉讲生动的故事了!变脸。此模式主要针对这些问题:

    • 对象之间的一对多的关系,设计的时候,不能是紧耦合的;
    • 被观察者状态更新的时候,观察者们能够自动更新;
    • 被观察者能通知所有的观察者们。

      直接僵硬的实现一对多的依赖,是非常简单的(紧耦合)。但会有很多明显的问题。紧耦合的对象设计,在我们需要扩展,改变,测试,或者复用的时候,是非常乏力的。

      观察者模式是一个使用率非常高的模式,最常用的地方是GUI系统、订阅-发布系统。因为这个模式的一个重要作用就是解耦,将观察者和被观察者解耦,使得它们之间的依赖性更小,甚至毫无依赖。以GUI系统来说,应用的UI具有易变性,尤其是前期随着业务的改变或者产品的需求修改,应用界面也会经常性变化,但是业务逻辑基本变化不大,此时,GUI系统需要一套机制来应对这种情况,使得UI层与具体的业务逻辑解耦,观察者模式测试就派上用场了。没错!就是大家平时说的MVC

    结构

      这里我用wiki中文的UML类图。


    wiki中文

      在上面的类图里面,Subject不是直接更新依附的具体Observer,而是通过接口方法,这样Subject状态的改变和具体Observer状态的更新就彼此独立了。这就使得Subject和Observers松耦合了。Subject和Observers不用知道彼此的逻辑。也可以在运行时灵活的添加或者移除某个Observer。下面具体介绍一下相关角色。

    • Subject:抽象主题,也就是被观察(Observable)的角色,抽象主题角色把所有观察者对象的引用保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
    • ConcreteSubject:具体主题,也就是具体被观察者(ConcreteSubject),该角色将有关状态存入具体观察者对象,在具体主题的内部状态有改变时,给所有注册过的观察者发出通知。
    • Observer:抽象观察者,定义一个更新接口,以便在主题的状态变化时,更新自身的状态。
    • ConcreteObserver:具体的观察者,该角色实际抽象观察者所定义的更新接口。

    怎么用“观察者模式”

    简单实现

      有了类图,实现非常简单,只需3步。《Head First设计模式》 一书中使用的WeatherData的例子blabla了一大堆,我这里一切从简,讲清楚就行。那本书中用的天气的数据显示,我这里用数据的二进制,十六进制的显示,3步搞定。如下。
      Step 1  创建Subject类。

    import java.util.ArrayList;
    import java.util.List;
    
    public class Subject {
        
       private List<Observer> observers = new ArrayList<Observer>();
       private int state;
    
       public int getState() {
          return state;
       }
    
       public void setState(int state) {
          this.state = state;
          notifyAllObservers();
       }
    
       public void attach(Observer observer){
          observers.add(observer);      
       }
    
       public void notifyAllObservers(){
          for (Observer observer : observers) {
             observer.update();
          }
       }    
    }
    

      Step 2  创建Observer抽象类。

    public abstract class Observer {
       protected Subject subject;
       public abstract void update();
    }
    

      Step 3  创建Observer具体类。
      二进制BinaryObverser.java。

    public class BinaryObserver extends Observer{
    
       public BinaryObserver(Subject subject){
          this.subject = subject;
          this.subject.attach(this);
       }
    
       @Override
       public void update() {
          System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
       }
    }
    

      十六进制HexaObverser.java。

    public class HexaObserver extends Observer{
    
       public HexaObserver(Subject subject){
          this.subject = subject;
          this.subject.attach(this);
       }
    
       @Override
       public void update() {
          System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() ); 
       }
    }
    

      好了,完事了。你可以用下面的代码来测试。

    public class ObserverPatternDemo {
       public static void main(String[] args) {
          Subject subject = new Subject();
    
          new HexaObserver(subject);
          new OctalObserver(subject);
          new BinaryObserver(subject);
    
          System.out.println("First state change: 15"); 
          subject.setState(15);
          System.out.println("Second state change: 10");    
          subject.setState(10);
       }
    }
    

      很容易得到以下输出。

    First state change: 15
    Hex String: F
    Binary String: 1111
    Second state change: 10
    Hex String: A
    Binary String: 1010
    

      怎么样,是不是《Head First设计模式》 一书中使用的WeatherData的例子一个样?这特么就是一个模子的啊!

      这里我提一下,到底用抽象还是接口?免得大家真正实现的时候,纠结这个问题。我先说一下我的理解:从概念上来看,接口和抽象类的概念不一样接口是对动作和行为的抽象,抽象类是对类的本质或者说根源的抽象。抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,猫啊,狗,喜羊羊啊,灰太狼啊,熊大啊,,他们的抽象类就可以定义成是动物。猫可以叫,狗也可以叫,我可以把“叫”定义成一个接口,然后让猫啊,狗啊这些类去实现它。这也是为什么,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。

      然而,真正落地到代码的时候,关注点往往不在这个概念上。比如有时候,TM已经继承了一个类了,想添加个行为,往往都是考虑加个接口吧,这就是大家说的多继承的根本原因。

      懂了么?从概念上来说,Subject和Observer应该是用抽象的,因为大家本质上就应该是对象,不是行为,我们可以把它们行为(attach,notify)等定义成接口,也可以直接做成抽象方法。但我对比了不同书籍的例子发现,别纠结,优雅好用就行!人是活的,不要被戒律了

    适用场景

      这里贴上我在一本书中看到的(只是想吐槽,反正我看着贼别扭)。

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

      这就是原文,然后我满怀希望的等着作者在具体解释一遍加深印象,可惜没有!!!看得我一点想理解的冲动都没有。我严重怀疑作者这段是不是在敷衍我?所以这里,我建议大家抽象出观察者模式几个重要特点,在特定场景中感觉符合这些特点,就可以用了。在我看来,Subject和Observer的关系,是非常简单纯粹的(比如,我在定义里面的灰太狼和光头强和意林的关系),关键在于订阅和自动更新这两个动作的抽象。所以我强调的情景中,主要针对这两个特点:

    • 当一个对象的数据更新时,需要通知其他对象(订阅)
    • 当一个对象的数据更新时,需要让其他对象各自更新自己的数据(自动更新)

      怎么样?是不是很容易理解了?

      最后,在这个小节我补充一下代码中常用的观察者模式吧。首先,Observer和Observable是JDK中的内置类型,这个模式的地位,可想而知了。在Android源码里面,适配器BaseAdapter通知列表数据更新,用的notifyChanged()就是典型的观察者模式;广播BroadcastReceiver就不多说了;组件之间通信用的事件总线框架EventBus;观察者模式通常与 MVC 范式有关系。在 MVC 中,观察者模式被用来降低 model 与 view 的耦合程度。

    总结

      观察者模式的使得观察者和被观察者解耦。

      优点

    • 观察者和被观察者之间是抽象耦合(松耦合),应对业务变化;
    • 增强系统灵活性、可扩展性。(符合开闭原则)

      缺点

    • 如果一个被观察者对象有很多的直接和间接的观察者的话,通知这个动作的执行有一定时间开销;
    • 在应用观察者模式时需要考虑一下开发效率和运行效率问题,程序中包括一个被观察者、多个观察者、开发和调试等内容会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般考虑采用异步的方式。

    相关文章

      网友评论

          本文标题:给大妈说“观察者模式”

          本文链接:https://www.haomeiwen.com/subject/ofugnxtx.html