行为型模式
1. 策略模式
策略模式是一种简单却常用的设计模式,它的应用场景非常广泛。我们先了解下策略模式的概念,再通过代码示例来更清晰的认识它。
策略模式由两部分构成:一部分是封装不同策略的策略组,另一部分是 Context。通过组合和委托来让 Context 拥有执行策略的能力,从而实现可复用、可扩展和可维护,并且避免大量复制粘贴的工作。
策略模式的典型应用场景是表单校验中,对于校验规则的封装。接下来我们就通过一个简单的例子具体了解一下:
/** * 登录控制器 */ function LoginController() { this.strategy = undefined; this.setStrategy = function (strategy) { this.strategy = strategy; this.login = this.strategy.login; } } /** * 用户名、密码登录策略 */ function LocalStragegy() { this.login = ({ username, password }) => { console.log(username, password); // authenticating with username and password... } } /** * 手机号、验证码登录策略 */ function PhoneStragety() { this.login = ({ phone, verifyCode }) => { console.log(phone, verifyCode); // authenticating with hone and verifyCode... } } /** * 第三方社交登录策略 */ function SocialStragety() { this.login = ({ id, secret }) => { console.log(id, secret); // authenticating with id and secret... } } const loginController = new LoginController(); // 调用用户名、密码登录接口,使用LocalStrategy app.use('/login/local', function (req, res) { loginController.setStrategy(new LocalStragegy()); loginController.login(req.body); }); // 调用手机、验证码登录接口,使用PhoneStrategy app.use('/login/phone', function (req, res) { loginController.setStrategy(new PhoneStragety()); loginController.login(req.body); }); // 调用社交登录接口,使用SocialStrategy app.use('/login/social', function (req, res) { loginController.setStrategy(new SocialStragety()); loginController.login(req.body); });
从以上示例可以得出使用策略模式有以下优势:
- 方便在运行时切换算法和策略
- 代码更简洁,避免使用大量的条件判断
- 关注分离,每个strategy类控制自己的算法逻辑,strategy和其使用者之间也相互独立
2. 观察者模式
观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一或一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。典型代表vue/react等。
使用观察者模式的好处:
- 支持简单的广播通信,自动通知所有已经订阅过的对象。
- 目标对象与观察者存在的是动态关联,增加了灵活性。
- 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。
当然给元素绑定事件的addEventListener()
也是一种:
target.addEventListener(type, listener [, options]);
Target就是被观察对象Subject,listener就是观察者Observer。
观察者模式中Subject对象一般需要实现以下API:
subscribe(): 接收一个观察者observer对象,使其订阅自己
unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己
fire(): 触发事件,通知到所有观察者
用JavaScript手动实现观察者模式:
// 被观察者 function Subject() { this.observers = []; } Subject.prototype = { // 订阅 subscribe: function (observer) { this.observers.push(observer); }, // 取消订阅 unsubscribe: function (observerToRemove) { this.observers = this.observers.filter(observer => { return observer !== observerToRemove; }) }, // 事件触发 fire: function () { this.observers.forEach(observer => { observer.call(); }); } }
验证一下订阅是否成功:
const subject = new Subject(); function observer1() { console.log('Observer 1 Firing!'); } function observer2() { console.log('Observer 2 Firing!'); } subject.subscribe(observer1); subject.subscribe(observer2); subject.fire();
验证一下取消订阅是否成功:
1. subject.unsubscribe(observer2); 2. subject.fire()
输出:
Observer 1 Firing!
3. 迭代器模式
ES6中的迭代器 Iterator 相信大家都不陌生,迭代器用于遍历容器(集合)并访问容器中的元素,而且无论容器的数据结构是什么(Array、Set、Map等),迭代器的接口都应该是一样的,都需要遵循 迭代器协议。
迭代器模式解决了以下问题:
- 提供一致的遍历各种数据结构的方式,而不用了解数据的内部结构
- 提供遍历容器(集合)的能力而无需改变容器的接口
一个迭代器通常需要实现以下接口:
hasNext()
:判断迭代是否结束,返回Booleannext()
:查找并返回下一个元素
为Javascript的数组实现一个迭代器可以这么写:
const item = [1, 'red', false, 3.14]; function Iterator(items) { this.items = items; this.index = 0; } Iterator.prototype = { hasNext: function () { return this.index < this.items.length; }, next: function () { return this.items[this.index++]; } }
验证一下迭代器:
1. const iterator = new Iterator(item); 2. 3. while(iterator.hasNext()){ 4. console.log(iterator.next()); 5. }
输出:
1, red, false, 3.14
ES6提供了更简单的迭代循环语法 for...of,使用该语法的前提是操作对象需要实现 可迭代协议(The iterable protocol),简单说就是该对象有个Key为 Symbol.iterator 的方法,该方法返回一个iterator对象。
比如我们实现一个 Range 类用于在某个数字区间进行迭代:
function Range(start, end) { return { [Symbol.iterator]: function () { return { next() { if (start < end) { return { value: start++, done: false }; } return { done: true, value: end }; } } } } }
验证:
1. for (num of Range(1, 5)) { 2. console.log(num); 3. }
结果:
1, 2, 3, 4
4. 状态模式
状态模式:一个对象有状态变化,每次状态变化都会触发一个逻辑,不能总是用if...else来控制。
比如红绿灯:
// 状态(红灯,绿灯 黄灯) class State { constructor(color) { this.color = color; } // 设置状态 handle(context) { console.log(`turn to ${this.color} light`); context.setState(this) } } // 主体 class Context { constructor() { this.state = null; } // 获取状态 getState() { return this.state; } setState(state) { this.state = state; } } // 测试 let context = new Context(); let green = new State('green'); let yellow = new State('yellow'); let red = new State('red'); // 绿灯亮了 green.handle(context); console.log(context.getState()) // 黄灯亮了 yellow.handle(context); console.log(context.getState()) // 红灯亮了 red.handle(context); console.log(context.getState())
设计原则验证
将状态对象和主体对象分离,状态的变化逻辑单独处理
符合开放封闭原则