博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Design Pattern: Observer Pattern
阅读量:6434 次
发布时间:2019-06-23

本文共 5976 字,大约阅读时间需要 19 分钟。

Brief                            

一直对Observer Pattern和Pub/Sub Pattern有所混淆,下面打算通过这两篇Blog来梳理这两种模式。若有纰漏请大家指正。

Use Case                          

首先我们来面对一个老到跌渣的故事,并以从未听说过Observer Pattern为前提。

假设要设计一个新闻订阅系统,新闻分为商业、体育和八卦3种,而查收终端有PC、移动终端等,后续还不断增加新闻种类和查收终端。
需求如上,下面我们根据OOD的方式来构建概念模型。
  新闻 <- 分类新闻
  终端 <- 分类终端
然后构造实体模型

// 新闻相关实体模型class NewsType{  constructor(){}}class BusinessNewsType extends NewsType{}class SportNewsType extends NewsType{}class EntertaintmentNewsType extends NewsType{}// 终端相关实体模型class Term{  getNews(news){}}class PCTerm extend Term{}class MobileTerm extend Term{}

接着我们关联已有经验——现实生活中的送报服务,发现用户A、用户B均订阅了A报的X早报,那么每天早上报纸刚印刷出来就会马上送到用户A和B那了。突然多了家订报有米送的Y早报,用户A和用户C跑去订阅,用户B直接就退了X早报,这时X早报为留住老用户就推送“老客户免费赠送半年X晚报”,于是用户A取消了退订的想法。

很明显 新闻订阅系统 就是线下业务直挪到线上的做法,通过分析线下业务流程我们可以找到设计方案。线下业务流程如下:
订阅:用户到报社订阅
退订:用户到报社退订
分发报纸:报社向所有订阅者分发报纸
按这思路构建 新闻订阅系统 的原型:

class NewsType{  constructor(){    this.subs = []  }  /* 订阅   * @param {Term} term - 终端   * @returns {Boolean}   */  sub(term){    // 排除    for(let _sub of this.subs)       if(_sub === term) return false    return Boolean(this.subs.push(term))  }  /* 退订   * @param {Term} term - 终端   * @return {Boolean}   */  unsub(term){    for(let i = 0, sub; sub = subs[i]; ++i)      if(sub === term) return Boolean(this.subs.splice(i, 1))  }  /*   * 分发新闻   */  notify(news){    for(let sub of this.subs) sub.getNews(news)  }}class BusinessNewsType extends NewsType{  pubNews(title, content){    var news = {title: title, content: content}    super.notify(news)  }}// definition of SportNewsType.......class PCTerm extends Term{  getNews(news){    alert(news.title + ';' + news.content)  }}class MobileTerm extends Term{  getNews(news){    console.log(news.title + ';' + news.content)  }}// 主程序则作为 新闻中心 与 用户交互的场所var businessNewsType = new BusinessNewsType()var sportNewsType = new SportNewsType()var pcTerm = new PCTerm()var mobileTerm = new MobileTerm()businessNewsType.sub(pcTerm)businessNewsType.sub(mobileTerm)sportNewsType.sub(mobileTerm)

上述原型基本勾勒出新闻订阅系统中对象及其关联的方式,我们就可以在这之上再细化和优化了。而从上述是原型我们不难发现 新闻 与 终端 均可独立开发,然后在主程序中做关联即可。新闻类型 和 终端类型的增删并不会对其他已有的新闻类型和终端类型有影响,除了在主程序中增删关联外。

现在我们作个简单的分析总结:
1. 不稳定因素(新闻类型 和 终端类型)解耦 -> 最小化不稳定因素所影响的范围(范围越小,后期改动越少);
2. 关联规则接口/契约化 -> 固化关联规则 和 关联发生的形式 便于后期维护。
这些是我面对未知问题的分析、解构方法,希望和大家一起探讨更美好的方法。

What Is Observer Pattern?                

Observer Pattern(观察者模式),狭义上是指Observer/Subscriber关注Observable/Subject的状态,并根据Observable/Subject的状态作出响应。广义上是指Observer/Subscriber关注Observable/Subject的状态或行为或两者兼备,并作出响应。

Roles

Observable/Subject(被观察者):定义被观察者的公共状态和行为

ConcreteObservable(具体的被观察者):定义具体的被观察者的状态和行为
Observer(观察者):定义观察者的公共状态和行为
ConcreteObserver(具体的观察者):定义具体的观察者的状态和行为

Two Methods: Push & Pull

上面第2节中的实现是由Observable/Subject来维护Observer组,那是不是只能这样呢?答案是否定的。它只是Push方式的实现,我们还可以采用Pull方式呢!

Push Model:推方式,也就是由Observable/Subject主动发起与Observer/Subscriber通信,并将自身的所有信息推给Observer/Subscriber,即使大部分信息最后都没用上。
pros: 1. 观察者实时响应被观察者的状态变化和行为状况;
cons: 1. 观察者被硬塞一些被观察者的无效信息;2. 被观察者状态变化频密,导致观察者忙于响应,消耗资源。
Pull Model:拉方式,也就是由主动Observer/Subscriber发起与Observable/Subject通信,并根据自身需要从Observable/Subject上拉去有效信息。一般通过定时器或特定事件触发执行。
pros: 1. 观察者可按需从被观察者处提取有效信息;2. 自主控制通信节奏,以免被状态频密变化的被观察者牵着鼻子走;
cons: 1. 获取被观察者状态变化上存在滞后甚至丢失的情况。
下面是Pull Model的实现方式

// Pull Model implementationclass FooNewsType{  constructor(){    this.news = []  }  addNews(title, content){    this.news.push({title: title, content: content, timestamp:(+new Date())})  }}class PCTerm{  constructor(){    this.subjects = []    this.newsTiles = []    this.lastPullDate = 0  }  subTo(newsType){    this.subjects.push(newType)  }  unsubFrom(newsType){    for(let i = 0, n; n = this.subjects[i]; ++i)      if(n === newsType) return this.subjects.splice(i, 1)  }  // 拉数据  pull(){    for(let sub of this.subjects)      for(let news of sub.news)        if(news.timestamp > this.lastPullDate)          this.newsTiles .push(news.title)  }}// 主程序var businessNewsType = new BusinessNewsType()var pcTerm = new PCTerm()pcTerm.subTo(businessNewsType)businessNewsType .addNews('Say Hi', 'Hello World!')// 其他代码........pcTerm.pull()

Improvement of Push Model

针对Push Model所带来的问题1,我们可以通过增强sub函数来解决

// definitionsub(term, aspect){  this.subs.push({term: term, aspect: aspect})}notify(news){  for(let sub of this.subs)     sub.term.getNews(sub.aspect && sub.aspect(news) || news)}// usagenew BusinessNewsType()  .sub(new PCTerm()        , (news)=>{ return news.title })

针对问题2,我们可以通过 定时推送通知 + 溢出通知 的方式解决,不过具体还是看业务需求咯

constructor(interval = 100, ceiling = 5){  this.ceiling = ceiling  this.timer = setInterval(()=>{    if (!this.pools.length || !this.subs.length) return    for(let sub of this.subs)      for(let n of news)        sub.term.getNews(sub.aspect && sub.aspect(n) || n)  }, interval)}notify(news){  this.pools.push(news)  if (this.pools.length < this.ceiling) return  var news = this.pools.splice(0, this.pools.length)  for(let sub of this.subs)    for(let n of news)      sub.term.getNews(sub.aspect && sub.aspect(n) || n)}

Specific Implementation Problems —— Making sure Subject state is self-consistent before notification

就是确保Subject状态变化完成后,再通知Subscriber。反例如下:

notify(news){  for(let sub of this.subs)     sub.term.getNews(sub.aspect && sub.aspect(news) || news)  // 发生在通知观察者之后  news.title = 'changed'}

相当于为每次Subject状态的整体变化打个版本号,然后将属于该版本的Subject状态发送给Subscriber,之后的状态变化就属于下一个版本了。

Diff Between Observer Pattern and Pub/Sub Pattern

两者区别主要体现在以下2点

1. 耦合度
Observer Pattern: Subscriber 和 Subject 两者感知对方的存在,但不受对方的具体实现 和 数目 所限制 => 弱依赖。关联规则内置在Subscriber 或 Subject中。
Pub/Sub Pattern: Publisher 和 Subscriber 两者相互间毫无存在感,通过Message Broker关联两种角色,并且将关联规则藏进Message Broker中。
2. 影响范围
  Observer Pattern作为Design Pattern存在,而Pub/Sub Pattern则作为Architecture Pattern存在,明显Observer Pattern的影响范围较小。也就是说在采用Pub/Sub Pattern时,需要更谨慎。

We Used Observer Pattern Already          

其实我们现在用到很多框架、类库均采用了Observer Pattern,如MVC和Event Mechanism等。

  MVC中M(odel)作为观察者,而V(iew)作为被观察者;
  而Event Mechanism则是更为典型的Observer Pattern,C#在语法层面(event关键字),而Java通过内置类库对其提供支持。

Conclusion                 

洋洋洒洒写了这么多,若有纰漏请大家指正,谢谢!

Thanks                      

转载地址:http://zfaga.baihongyu.com/

你可能感兴趣的文章
XSS跨站脚本***
查看>>
linux 挂载光驱
查看>>
ASP.NET MVC Area操作
查看>>
CSS颜色代码大全
查看>>
LINQ之路10:LINQ to SQL 和 Entity Framework(下)
查看>>
circle area
查看>>
怎么改变按钮的图标
查看>>
当输入流和输出流同时作用一个文件
查看>>
MySQL关于表碎片整理OPTIMIZE TABLE操作
查看>>
FortiGate 0458版本bug
查看>>
Java --- 多线程 面试题
查看>>
OA项目如何成功实施!
查看>>
FindMaxConsecutive.java
查看>>
面试官问:ZooKeeper 一致性协议 ZAB 原理
查看>>
DNS实现域名正解与反解
查看>>
Linux学习-标准输入输出
查看>>
CentOS 7 配置IP
查看>>
文本处理工具grep及正则表达式
查看>>
Intel VT-x处于禁用状态
查看>>
用什么软件可以修改PDF文件,软件的操作方法
查看>>