JavaScript-设计模式-观察者模式

  1. 参考:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#prototypepatternjavascript
  2. 参考:JavaScript编程精粹
1. 观察者模式
  1. GOF的《设计模式》:

“One or more observers are interested in the state of a subject and register their interest with the subject by attaching themselves. When something changes in our subject that the observer may be interested in, a notify message is sent which calls the update method in each observer. When the observer is no longer interested in the subject’s state, they can simply detach themselves.”

  1. JavaScript编程精粹对这段话的翻译是

对目标状态感兴趣的一个或者多个观察者,通过将自身与目标关联在一起的形式进行注册。当目标出现观察者可能感兴趣的变化时,发出提醒消息,进而调用每个贯彻者的更新方法。如果观察者对目标不再感兴趣,只需要解除关联即可。

观察者模式,简单来说就是多个观察者观察一个目标,目标内容发生变化时,目标可以主动通知观察者,观察者可以在收到通知时乘机作出反应。

2. 实现

首先,明确两个概念:

  • Subject(目标)
  • Observer(观察者)

那么,被观察的目标如何在变化的时候通知观察者呢?
需要让被观察的目标保留对观察者的引用。
这样在目标内部发生变化的时候,目标可以通过保留的观察者的引用,来调用观察者。

首先,定义一个目标。
然后,利用目标保存观察者。
当目标发生变化的时候,通知观察者。

class Subject {
  constructor() {
    this.observer_list = [];
  }
  // 添加观察者
  add_observer = function( observer ) {
    this.observer_list.push( observer );
  }
  // 移除观察者
  remove_observer = function( observer ) {
    let observer_index = this.observer_list.findIndex((item)=>item === observer);
    this.observer_list.splice(observer_index, 1);
    console.log("移除观察者")
  } 
  // 通知观察者
  notify = function() {
    for (let observer of this.observer_list) {
      observer.update();	// 调用观察者的update函数
    }
  }
}
// 观察者1号
let Observer1 = {
  update: function() {
    console.log('观察者1号 被通知了')
  }
}
// 观察者2号
let Observer2 = {
  update: function() {
    console.log('观察者2号 被通知了')
  }
}

let mySubject = new Subject();
mySubject.add_observer(Observer1);	// 添加观察者1号
mySubject.add_observer(Observer2);	// 添加观察者2号
mySubject.notify();
mySubject.remove_observer(Observer1);	// 移除观察者1号
mySubject.remove_observer(Observer2);	// 移除观察者2号

结果:

观察者1号 被通知了
观察者2号 被通知了
移除观察者
移除观察者

具体通知内容,利用update传递参数即可。

class Subject {
  constructor() {
    this.observer_list = [];
  }
  // 添加观察者
  add_observer = function( observer ) {
    this.observer_list.push( observer );
  }
  // 移除观察者
  remove_observer = function( observer ) {
    let observer_index = this.observer_list.findIndex((item)=>item === observer);
    this.observer_list.splice(observer_index, 1);
    console.log("移除观察者")
  } 
  // 通知观察者
  notify = function() {
    let args = arguments || [];
    for (let observer of this.observer_list) {
      observer.update(...args);
      // observer.update();
    }
  }
}

let Observer1 = {
  update: function() {
    let args = arguments || [];
    console.log('观察者1号 被通知了, 通知内容为 - ' , ...args)
  }
}

let Observer2 = {
  update: function() {
    let args = arguments || [];
    console.log('观察者2号 被通知了, 通知内容为 - ' , ...args)
  }
}

let mySubject = new Subject();
mySubject.add_observer(Observer1);
mySubject.add_observer(Observer2);
mySubject.notify("...这里是目标...");
mySubject.remove_observer(Observer1);
mySubject.remove_observer(Observer2);

结果:

观察者1号 被通知了, 通知内容为 -  ...这里是目标...
观察者2号 被通知了, 通知内容为 -  ...这里是目标...
移除观察者
移除观察者
3. 参考一下

JavaScript编程精粹的大致示例:

let Subject = (function() {
  function Subject() {
    this.observer_list = [];
  }
  // 向内部列表添加观察者
  Subject.prototype.add_observer = function ( obj ) {
    this.observer_list.push( obj );
  };
  // 向内部列表删除观察者
  Subject.prototype.remove_observer = function ( obj ) {
    this.observer_list.splice( this.observer_list.findIndex( (item)=>item === obj ) , 1 );
  }
  Subject.prototype.notify = function () {
    let args = arguments || [];
    for (observer of this.observer_list) {
      observer.update(...args); // 调用观察者的update方法
    }
  }
  return Subject;
})()

function Tweeter() {
  this.subject = new Subject();

  this.add_observer = function ( observer ) {
    this.subject.add_observer( observer );
  }

  this.remove_observer = function ( observer ) {
    this.subject.remove_observer( observer );
    console.log("Remove Observer");
  }

  this.fetchTweeter = function() {
    let tweet = {
      tweet: "This is one apple."
    }
    this.subject.notify(tweet);
  }
}

// 添加观察者
let TweetUpdater = {
  update: function() {
    console.log( 'Updated Tweet - ' + arguments );
  }
};

let TweetFollower = {
  update: function() {
    console.log( 'Following this tweet - ' + arguments );
  }
};

let TweetApp = new Tweeter();
TweetApp.add_observer(TweetUpdater);
TweetApp.add_observer(TweetFollower);
TweetApp.fetchTweeter();
TweetApp.remove_observer(TweetUpdater);
TweetApp.remove_observer(TweetFollower);

结果:

Updated Tweet - [object Arguments]
Following this tweet - [object Arguments]
Remove Observer
Remove Observer

书上的"栗子",首先是定义了一个Subject函数,该函数内部包括一个观察者列表(observer_list),一个添加函数(add_observer_list),一个删除函数以及一个通知函数。
然后,定义了一个Tweeter函数,该函数有一个成员变量,为Subject类型。
再然后,定义了两个观察者,观测目标的变化。

TweetApp去fetchTweeter的时候,TweetUpdater、TweetFollower会得到通知。

栗子

利用一个按钮添加复选框,每个复选框都观测按钮。当,原有的复选框发生变化时,会通知按钮,然后按钮会通知新添加的所有复选框。(案例来自,《Learning JavaScript Design Patterns》,略作修改)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
    <button id="addNewObserver">添加复选框</button>
    <input id="mainCheckBox" type="checkbox">
    <div id="observersContainer"></div>
  </div>
  <script>
    var Subject = (function() {
      function Subject() {
        this.observer_list = [];
      }
      // 向内部列表添加观察者
      Subject.prototype.add_observer = function ( obj ) {
        this.observer_list.push( obj );
      };
      // 向内部列表删除观察者
      Subject.prototype.remove_observer = function ( obj ) {
        this.observer_list.splice( this.observer_list.findIndex( (item)=>item === obj ) , 1 );
      }
      Subject.prototype.notify = function () {
        let args = arguments || [];
        for (observer of this.observer_list) {
          observer.update(...args); // 调用观察者的update方法
        }
      }
      return Subject;
    })()
    // 拓展属性
    function extend( obj, extension ) {
      for (let key in extension) {
        obj[key] = extension[key]; 
      }
    }
    let controlCheckBox = document.getElementById('mainCheckBox');
    let addBtn = document.getElementById('addNewObserver');
    let container = document.getElementById('observersContainer');

    // 用目标类去拓展controlCheckBox
    extend( controlCheckBox, new Subject() );
    addBtn.onclick = addNewObserver;
    
    function addNewObserver() {
      let check = document.createElement('input');
      check.type = 'checkbox';

      // 重写更新函数
      check.update = function ( value ) {
        this.checked = value;
      }

      // 目标添加观察者
      controlCheckBox.add_observer( check );
      // 添加到容器
      container.appendChild( check );
    }

    mainCheckBox.onchange = function(e) {
      console.log(e.target.checked)
      controlCheckBox.notify(e.target.checked)
    }
  </script>
</body>
</html>
4. 观察者模式的优缺点
5. 观察者模式和订阅/发布模式的区别
  1. 区别
    观察者模式,观察者和被观察的目标直接接触。
    订阅/发布模式,发布者订阅者不直接接触,两者通过中间的管道进行通信。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值