观察者模式
The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state.
观察者是一种 包含一系列依赖于主体(subject)的观察者(observers),自动通知它们变化的内容的设计模式
接下来,用oberver pattern来实现一个购物车列表,实现之前先说明几个概念:
主体 (Subject)
维护一系列观察者的数组,包含一些操作方法,比如增加、删除,获取等等。
class Subject {
constructor() {
this.observerList = [];
}
add(item) { //添加
return this.observerList.push(item);
}
count() { //获取长度
return this.observerList.length;
}
get(index) { //获取下标
if (index > -1 && index < this.observerList.length) {
return this.observerList[index];
} else {
return null;
}
}
notify(context) { //通知
let observerCount = this.count();
for (let i = 0; i < observerCount; i++) {
console.dir(this.get(i))
this.get(i).update(context);
}
}
}
首先我们声明一个主体类,里面包含一个观察者数组,还有一些操作方法。
观察者(Observer)
class Observer {
constructor() {
this.update = () => { //通用interface
//todo
}
}
}
声明一个更新接口,用来获取主体分发的通知。
两者关系大概如下:
主要流程:
- 定义好主体类和观察者类
- 类实例化成具体对象,绑定方法,观察者在主体对象里注册
- 操作。主体分发消息给观察者。
具体实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
table tr td {
text-align: center;
}
</style>
</head>
<body>
<p>
name:
<input type="text" style="width:100px" id="name"> price:
<input type="text" style="width:50px" id="price">
<input type="button" id="btn" value="Add">
<input type="text" style="width:50px" id="priceAdd">
<input type="button" id="priceAddBtn" value="priceAdd">
</p>
<table>
<caption>list</caption>
<thead>
<tr>
<th>
name
</th>
<th>
price
</th>
<th>
action
</th>
</tr>
</thead>
<tbody id="main">
</tbody>
</table>
<script>
//被观察类
class Subject {
constructor() {
this.observerList = [];
}
add(item) { //添加
return this.observerList.push(item);
}
count() { //获取长度
return this.observerList.length;
}
get(index) { //获取下标
if (index > -1 && index < this.observerList.length) {
return this.observerList[index];
} else {
return null;
}
}
notify(context) { //通知
let observerCount = this.count();
for (let i = 0; i < observerCount; i++) {
console.dir(this.get(i))
this.get(i).update(context);
}
}
}
//观察类
class Observer {
constructor() {
this.update = () => { //通用interface
//todo
}
}
}
//合并对象方法
let extend = (source, extendsion) => {
let ext = new extendsion(),
properties = Object.getOwnPropertyNames(ext),
protoProperties = Object.getOwnPropertyNames(extendsion.prototype);
// to get all properties
properties.forEach(function(val, key) {
source[val] = ext[val];
if (key == properties.length - 1) {
protoProperties.forEach(function(innerVal, key) {
if (val != 'constructor') {
source[innerVal] = extendsion.prototype[innerVal];
}
})
}
})
}
let btn = document.getElementById('btn');
let priceAdd = document.getElementById('priceAddBtn');
//挂载主体
extend(btn, Subject);
//extend(priceAdd, Subject);
//按钮
btn.addEventListener('click', function() {
let tBody = document.getElementById('main'),
name = document.getElementById('name'),
price = document.getElementById('price'),
dataSet = {
name: name.value,
price: price.value
};
let html = document.createElement('tr');
//模版
let item =
`
<td class="name">${dataSet.name}</td><td class="price">${dataSet.price}</td><td><button type="button" onclick='del(this)'>delete</button></td>
`;
html.innerHTML = item;
btn.add(html); //observer放入subject
extend(html, Observer); //继承观察类
html.update = function(context) { //更新context
let num = Number(this.querySelector('.price').innerHTML);
this.querySelector('.price').innerHTML = num + context;
};
tBody.appendChild(html);
});
priceAdd.addEventListener('click', function() {
let num = document.getElementById('priceAdd').value;
btn.notify(Number(num));
})
//删除当前行
function del(obj) {
let _this = obj,
box = _this.parentNode.parentNode.parentNode;
box.removeChild(_this.parentNode.parentNode);
}
</script>
</body>
</html>
发布/订阅者模式
The Publish/Subscribe pattern however uses a topic/event channel which sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application specific events which can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher.
Publish/Subscribe pattern和Observer pattern和类似,都是Observer注册,subject分布通知,但是Publish/Subscribe pattern多了个事件管道(event channel)用来集中处理监听的事件
典型的Publish/Subscribe模式的实现代码:
var pubsub = {};
(function(myObject) {
// Storage for topics that can be broadcast
// or listened to
var topics = {};
// An topic identifier
var subUid = -1;
// Publish or broadcast events of interest
// with a specific topic name and arguments
// such as the data to pass along
myObject.publish = function(topic, args) {
if (!topics[topic]) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
return this;
};
// Subscribe to events of interest
// with a specific topic name and a
// callback function, to be executed
// when the topic/event is observed
myObject.subscribe = function(topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
// Unsubscribe from a specific
// topic, based on a tokenized reference
// to the subscription
myObject.unsubscribe = function(token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return this;
};
}(pubsub));
test demo:
//注册事件
var test = pubsub.subscribe('message', function(topic, data) {
console.log("topic:" + topic + ",data:" + data);
});
//分布消息
pubsub.publish('message', "siip"); //topic:message,data:test,a,b,c
pubsub.publish("message", ["test", "a", "b", "c"]); //topic:message,data:test,a,b,c
//删除注册事件
pubsub.unsubscribe(test);
pubsub.publish("message", {
sender: "hello@google.com",
body: "Hey again!"
});
两者关系大概如下:
参考:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。