知识点回顾,上次主要说了函数式和面向对象,命令式和响应式,push 系统和 pull 系统的差别。在编程范式,风格之外,设计模式也是在程序设计中时时刻刻都在使用的东西,今天主要就讨论一下设计模式这个东西。
什么是设计模式
模式是一种可复用的解决方案,它有三大好处:
- 模式是已经得到验证的解决方案,因此我们可以在适合的场景中放心的使用它。
- 模式很容易被复用,是一种立即可用的解决方案,而且可以对其适当的修改以满足个性化的需求。
- 模式富有表达力,它通常有很良好的结构及已经设置好的表达方案的词汇,可以非常轻松的表达出程序员的意图。
其实我们每天都在接触模式,从最简单的facade(外观模式,jQuery,lodash为代表)到 singleton,再到MVC,模式可以说无处不再,当然还有rxjs中使用观察者模式等。有模式,就有反模式,既然模式可以带来好处,相应的反模式就会带来坏处,在javaScript中,以下就是我们经常见到的反模式:
- 在全局上下文中定义大量的变量来污染全局命名空间。这里的全局我们应该以相对的思维考虑,而不是特指window对象。例如:在angularjs 中一个controller 作为封闭的作用域,对于它内部定义的各种变量来说,这个作用域就是全局的,写过angularjs的同学应该遇到过,在一个controller中定义很多的变量,然后随着功能的增加,越来越难以维护。
- 修改类的原型,尤其是Object类,比修改更过份的是直接替换。
- 以内联的形式使用javaScript。
- 给setTimeout 或 setInterval传递字符串而不是函数,这会触发内部 eval 的执行。
响应式中的设计模式
观察者模式
在这种模式中,一个对象维持一系列依赖于它的对象,将有关的状态变更自动的通知给它们。当我们不再希望某个特定的观察者获取注册目标的对象时,它可以从目标的观察者列表中移除。代码如下:
观察者列表类,我们利用这个类来维护观察者的增删改查
class ObserverList {
constructor() { };
list = [];
add(observer) {
this.list.push(observer);
}
count() {
return this.list.length;
}
get(index) {
if(index > -1 && index < this.list.length) {
return this.list[index];
}else {
return null;
}
}
indexOf(observer, startIndex) {
let i = startIndex;
let pointer = -1;
while( i< this.list.length) {
if(this.list[i] === observer) {
pointer = i;
}
i++;
}
return pointer;
}
removeIndexAt(index) {
if(index === 0) {
this.list.shift();
}else if (index === this.list.length - 1) {
this.list.pop();
}
}
}
主题类,利用这个类来维护一个观察目标,使用观察者列表类来维护其自己的观察者,通过观察者提供的接口向外发送目标上发生的变化。
class Subject {
constructor() {
this.observers = new ObserverList();
}
addObserver(observer) {
this.observers.add(observer);
}
removeObserver(observer) {
this.observers.removeIndexAt(this.observers.indexOf(observer, 0));
}
notify(context) {
const count = this.observers.count();
for(let i = 0; i< count; i++) {
this.observers.get(i).update(context);
}
}
}
观察者类,为目标发生变化时需要获得通知的对象提供一个更新接口。
class Observer {
constructor() { }
update() {
// 获取通知的接口, 不同的observe 可以针对性的设置更新逻辑
}
}
然后我们就可以利用定义好的这些类,实现一些功能,例如,一个主checkbox,当它的状态变化时通知页面上其它的checkbox检查状态,代码大致如下:
HTML代码
<button id="button">Add Observer</button>
<input id="box" type="checkbox">
<div id="container"></div>
javaScript代码
const box = document.getElementById('box');
const btn = document.getElementById('button');
const container = document.getElementById('container');
// 工具函数
function extend(source, target) {
for (let key in source) {
target[key] = source[key];
}
}
// 利用工具函数来扩展DOM元素
extend(new Subject(), box);
// 将点击事件通知给观察者
box.onclick = function {
box.notify(box.checked);
}
btn.onclick = function addNewObserver() {
const check = document.createElement('input');
check.type = 'checkbox';
extend(new Observer(), check);
// 重写自定义的更新行为
check.update = (value) => this.checked = value;
// 为subject的观察者列表中添加新的观察者
box.addObserver(check);
// 将观察者附加到容器上
container.appendChild(check);
}
发布/订阅模式
观察者模式要求希望接收通知的观察者必须订阅内容改变的事件,而发布/订阅模式中添加了一个事件通道,此通道介于订阅者和发布者之间,这样设置的主要目的是促进发布者和接收者之间的松散耦合,避免订阅者和发布者产和直接的联系,如图:
Observer Pattern
/----<--Subscribe--<--\
Subject Observer
\--->--Fire Event-->--/
Publish/Subscribe Pattern
/----<--Subscribe--<--\
Publisher-->--publish-->---Event Channel Subscriber
\--->---fire event-->--/
在实际的应用中, 这两种模式可以结合使用,它们都鼓励开发者思考应用程序之间不同部分之间的关系,将应用程序分解为更小,更松散耦合的块以提高代码的复用。
这两种模式的优缺点
优点
- 只需要维护各个对象之间的通信接口的一致性,而无需紧密耦合。
- 观察者和目标之间可以建立起一种动态的关系,这提供了很大的灵活性,在程序的各部分紧密耦合时,要实现这种动态关系是非常不容易的。
缺点
- 在发布/订阅模式中,由于解耦了发布者和订阅者,有时会难以保证程序按照我们的预期进行。例如,发布者会假设有人在订阅它们,当订阅者发生错误后,由于系统的解耦,发布者并不会看到这一点。
- 订阅者之间非常无视彼此的存在,对于变换发布者产生的成本视而不见,由于它们之间动态的关系,难以跟踪依赖更新。
rxjs的实现就是建立在这两种模式的基础之上,预先要了解的基本知识通过这两篇基本上介绍完了,当然都是走马观花式的,基中任何一个点拿出来都可以长篇大论,各位如有兴趣可以找资料深入研究。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。