观察者模式
目的
通知对象的变化到另一些对象
何时使用
- 某些对象的状态改变需要触发另一些对象的改变
- 广播能力
常见使用场景
在GUI系统中,一些按钮,菜单发出的通知。
代码实现
https://github.com/benhaben/essentialjsdesignpatterns.git
下面的代码实现了点击一个checkbox,通知所有其他的checkbox。checkbox可以通过“Add New Observer checkbox”按钮来增加。
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>观察者模式</title>
</head>
<body>
<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>
<script type="text/javascript" src="observer.js"></script>
</body>
</html>
js:
function Subject(){
this.observers = new ObserverList();
function ObserverList(){
this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
if( index > -1 && index < this.observerList.length ){
return this.observerList[ index ];
}
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
var i = startIndex;
while( i < this.observerList.length ){
if( this.observerList[i] === obj ){
return i;
}
i++;
}
return -1;
};
ObserverList.prototype.removeAt = function( index ){
this.observerList.splice( index, 1 );
};
}
Subject.prototype.addObserver = function( observer ){
this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
var observerCount = this.observers.count();
for(var i=0; i < observerCount; i++){
this.observers.get(i).update( context );
}
};
// ------------------------------------------------------------
function Observer(){
this.update = function(){
// ...
};
}
// ------------------------------------------------------------
// Extend an object with an extension
function extend( obj, extension ){
for ( var key in extension ){
obj[key] = extension[key];
}
}
// References to our DOM elements
var controlCheckbox = document.getElementById( "mainCheckbox" ),
addBtn = document.getElementById( "addNewObserver" ),
container = document.getElementById( "observersContainer" );
// Concrete Subject
// Extend the controlling checkbox with the Subject class
extend( controlCheckbox, new Subject() );
// Clicking the checkbox will trigger notifications to its observers
controlCheckbox.onclick = function(){
controlCheckbox.notify( controlCheckbox.checked );
};
addBtn.onclick = addNewObserver;
// Concrete Observer
function addNewObserver(){
// Create a new checkbox to be added
var check = document.createElement( "input" );
check.type = "checkbox";
// Extend the checkbox with the Observer class
extend( check, new Observer() );
// Override with custom update behaviour
check.update = function( value ){
this.checked = value;
};
// Add the new observer to our list of observers
// for our main subject
controlCheckbox.addObserver( check );
// Append the item to the container
container.appendChild( check );
}
Publish/Subscribe Pattern
在JS的世界中有一种类似的模式,叫推送/注册模式,可以让observer和subject解耦。
推送/注册模式可以设置一个“频道”,让感兴趣的注册这实现一个函数去注册。
感觉推送/注册模式更能发挥js的“动态特性”,而observe更适合具有静态特性和具有接口的语言。
代码实现
下面的列子基于inbox/newMessage这个频道,其实也就是一个字符串来标示一下。可以用node来跑这个例子。
/**
* Created by shenyin.sy on 17/3/16.
*/
"use strict"
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
subscribe(eventName, listener) {
this.on(eventName, listener);
}
publish() {
var args = Array.prototype.slice.call(arguments);
this.emit.apply(this, args);
}
}
const myEmitter = new MyEmitter();
// A very simple new mail handler
// A count of the number of messages received
var mailCounter = 0;
// Initialize subscribers that will listen out for a topic
// with the name "inbox/newMessage".
// Render a preview of new messages
myEmitter.subscribe("inbox/newMessage", function (topic, data) {
// Log the topic for debugging purposes
console.log("A new message was received, show topic: ", topic);
});
// Here's another subscriber using the same data to perform
// a different task.
// Update the counter displaying the number of new
// messages received via the publisher
myEmitter.subscribe("inbox/newMessage", function (topic, data) {
console.log("A new message was received, show data: ", data);
});
myEmitter.publish("inbox/newMessage", [{
sender: "hello@google.com",
body: "Hey there! How are you doing today?"
}], "this is data");
// We could then at a later point unsubscribe our subscribers
// from receiving any new topic notifications as follows:
// unsubscribe( subscriber1 );
// unsubscribe( subscriber2 );
总结
对象的状态改变,需要通知给另一些对象,使用这个模式是比较靠谱的。