观察者模式,从公众号群发说起

  • 2019 年 10 月 3 日
  • 筆記

每个人应该都订阅了不少微信公众号,那你有没有注意到微信公众号的消息呢?你订阅的公众号号主每发布一篇文章,你都会主动的接收到文章的推送,并不需要你点开每个订阅的公众号一一查看有没有更新,是不是觉得有点意思?感兴趣?那就接着往下看吧,因为接下来我们要模拟公众号群发的场景。

要模拟公众号群发,首先需要简单的了解一下公众号的特点,对于公众号的特点,我总结了以下三点:

  • 每个公众号会有多名订阅者,公众号跟订阅者在某种层面上是一对多的关系
  • 只有订阅者才能在公众号发布新文章时,会及时接收到推送通知,没有订阅公众号的阅读者不会接收到文章推送通知。
  • 每个订阅者都依赖于公众号号主,只有公众号号主发布文章,订阅者才有文章查看

现在业务场景我们大概知道了,那就开始动手编写我们的业务吧,我们先从公众号号主开始。

对于公众号号主,我们先理解一下公众号特点的第二点:只有订阅者才能在公众号发布新文章时,会及时接收到推送通知,没有订阅公众号的阅读者不会接收到文章推送通知。这个特点说明在公众号号主这边维护者订阅者的列表,在每次发布文章时会通知列表中的每一个订阅者告诉他们有新文章了。如果号主没有订阅者列表,那怎么知道需要通知哪些人呢?对于这个订阅者列表,号主肯定有增删的权利,毕竟这个公众号你说了算。根据上面分析的,我们给号主做出一个抽象,我们建立一个抽象的Author类。Author类具体设计如下:

/**   * 号主抽象类   */  public interface Author {      // 添加关注者      void addReader(Reader reader);      // 删除关注者      void deleteReader(Reader reader);      // 通知关注者      void notifyReader();      // 写文章      void writeArticle(String article);  }

在我们的场景中号主主要有添加订阅者、删除订阅者、通知订阅者和写文章的功能,号主有了,接下来就是我们的订阅者,订阅者在我们的场景中就比较简单了,只有一个阅读的功能,我们定义一个阅读者抽象类ReaderReader类的具体设计如下:

/**   * 阅读者接口   */  public interface Reader {      // 阅读文章      void reader(String authorName,String article);  }

号主和阅读者的接口都定义好了,下面我们就需要真正的号主和订阅者了。我们建立我们第一个号主平头哥PingtougeAuthorPingtougeAuthor类的设计如下:

/**   * @author 平头哥   * @title: PingtougeAuthor   * @projectName observer   * @description: 号主平头哥   * @date 2019/9/1817:50   */  public class PingtougeAuthor implements Author{      // 订阅者列表      private Vector<Reader> readers ;      // 作者名称      private String name;      // 文章      private String article;        public PingtougeAuthor(String name){          this.name = name;          this.readers = new Vector<>();      }      /**       * 添加关注者       * @param reader       */      @Override      public void addReader(Reader reader) {          if (readers.contains(reader)) return;          readers.add(reader);      }      /**       * 移除关注者       * @param reader       */      @Override      public void deleteReader(Reader reader) {          readers.remove(reader);      }      /**       * 通知关注者       */      @Override      public void notifyReader() {          for (Reader reader:readers){              reader.reader(name,article);          }      }      /**       * 写文章       * @param article       */      @Override      public void writeArticle(String article){          this.article = article;          notifyReader();      }  }

我们在建立王山、张三、李四三个订阅者,他们的具体设计如下:

/**   * 订阅者王山   */  public class WangSanReader implements Reader{      private String name;      public WangSanReader(String name){          this.name = name;      }      @Override      public void reader(String authorName,String article) {          System.out.println(name+" 阅读了 "+authorName+" 发布的 "+article+" 文章");      }  }
/**   * 订阅者张三   */  public class ZhangsanReader implements Reader{      private String name;        public ZhangsanReader(String name){          this.name = name;      }        @Override      public void reader(String authorName,String article) {          System.out.println(name+" 阅读了 "+authorName+" 发布的 "+article+" 文章");      }  }
/**   * 订阅者者李四   */  public class LiSiReader implements Reader{      private String name;        public LiSiReader(String name){          this.name = name;      }      @Override      public void reader(String authorName,String article) {          System.out.println(name+" 阅读了 "+authorName+" 发布的 "+article+" 文章");      }  }

号主和订阅者都有了,万事俱备只欠东风,那我们就来进行我们第一次场景模拟,在这次模拟中,订阅者张三、王山订阅了平头哥的公众号,李四没有订阅平头哥的公众号。按照我们的设想平头哥发布文章时,张三、王山可以接收到文章推送通知,李四不会接收到文章推送通知。编写我们的第一个模拟类:

public class App {      public static void main(String[] args) {          PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平头哥");            WangSanReader wangSanReader = new WangSanReader("王山");          ZhangsanReader zhangsanReader = new ZhangsanReader("张三");          LiSiReader liSiReader = new LiSiReader("李四");            // 王山订阅了平头哥的公众号          pingtougeAuthor.addReader(wangSanReader);          // 张三订阅了平头哥的公众号          pingtougeAuthor.addReader(zhangsanReader);            pingtougeAuthor.writeArticle("看完这篇你还不知道这些队列,我这些图白作了");      }  }

测试结果:

从测试结果中,我们可以看出张三、王山接收到了平头哥发布文章的推送通知,李四没有接收到,符合我们的预期。

接下来我们进行第二次模拟,由于平头哥最近发了不少干货,李四决定也关注平头哥的公众号,所以我们对模拟类进行了修改,修改后的模拟类如下:

public class App {      public static void main(String[] args) {          PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平头哥");          WangSanReader wangSanReader = new WangSanReader("王山");          ZhangsanReader zhangsanReader = new ZhangsanReader("张三");          LiSiReader liSiReader = new LiSiReader("李四");            // 王山订阅了平头哥的公众号          pingtougeAuthor.addReader(wangSanReader);          // 张三订阅了平头哥的公众号          pingtougeAuthor.addReader(zhangsanReader);  //  //        pingtougeAuthor.writeArticle("看完这篇你还不知道这些队列,我这些图白作了");          // 李四也订阅平头哥的公众号          pingtougeAuthor.addReader(liSiReader);            pingtougeAuthor.writeArticle("实现 Java 本地缓存,该从这几点开始");        }  }

测试结果:

这次三个订阅者都接收到了平头哥发布文章的推送通知。关注了平头哥的公众号有一段时间后,张三觉得平头哥的公众号不适合自己,于是就取关了平头哥的公众号,这是我们要模拟的第三个场景,我们对模拟类进行修改,修改后的模拟类如下:

public class App {      public static void main(String[] args) {          PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平头哥");          WangSanReader wangSanReader = new WangSanReader("王山");          ZhangsanReader zhangsanReader = new ZhangsanReader("张三");          LiSiReader liSiReader = new LiSiReader("李四");            // 王山订阅了平头哥的公众号          pingtougeAuthor.addReader(wangSanReader);          // 张三订了平头哥的公众号          pingtougeAuthor.addReader(zhangsanReader);  //  //        pingtougeAuthor.writeArticle("看完这篇你还不知道这些队列,我这些图白作了");            // 李四订了平头哥的公众号          pingtougeAuthor.addReader(liSiReader);    //        pingtougeAuthor.writeArticle("实现 Java 本地缓存,该从这几点开始");          // 张三取关了平头哥的公众号          pingtougeAuthor.deleteReader(zhangsanReader);          pingtougeAuthor.writeArticle("实观察者模式,从微信公众号说起");        }  }

测试结果:

张三取关之后,平头哥发布的文章,他将不会再接收到推送通知。在上面公众号群发的模拟场景中,我们使用到了一种设计模式,叫做观察者模式,那今天我们就一起来简单的了解一下观察者模式。

观察者模式定义

观察者模式又叫发布-订阅模式,发布-订阅模式大家应该非常熟悉了吧,消息中间件就是发布-订阅模式,观察者模式主要也是应用在消息中间件。观察这模式定义了一种一对多的依赖关系让多个订阅者同时监听某一个对象主题,这个主题对象在状态发生变化时,会通知所有的订阅者,让他们自己更新自己。这些特点在我们的模拟场景中基本上都体现出来了,如果你对这些有疑问,可以多看我们的模拟场景。

观察者的结构

  • 抽象主题(Subject)角色:抽取出了主题所需要定义的接口,比如我们的Author
  • 具体主题(Concrete Subject)角色:具体的主题实现者,该类必须继承抽象主题,比如我们的PingtougeAuthor
  • 抽象观察者(Observer)角色:观察者抽象类,定义观察者需要的接口,比如我们的Reader
  • 具体观察者(Concrete Observer)角色:具体的观察者,做这具体业务的类,比如我们的三个订阅者

观察者的 UML 图

观察者模式的优点

  • 主题与观察者之间松耦合
  • 支撑广播通信:一条消息可以通知给多个人
  • 建立一条触发链:A触发B,B触发C,一条触发链,不过这个需要注意的地方很多

观察者的缺点

  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
  • 如果采用顺序通知,当某个观察者卡住了,其他的观察者将无法接收到通知
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃

观察者模式差不多就这些内容,相对来说比较容易理解,另外多说一句,在JDK中已经内置了观察者模式,在java.util下的ObservableObserver两个类,有兴趣的可以去了解一下。

源代码

文章不足之处,望大家多多指点,共同学习,共同进步

最后