简明JavaScript设计模式
零、软件设计原则与设计模式的分类
0.1 软件设计原则-SOLID
0.1.1 单一职责原则-SRP
误解:每个模块应该只完成一个功能,我们在将大型函数重构成小函数驶经常会用到,但这只是面向底层实现细节的设计原则。
任何一个软件模块都应该只对某一类行为者负责。
0.1.2 开闭原则-OCP
软件系统应允许通过新增代码来修改系统行为,而非只能靠修改原来的代码。
0.1.3 里氏替换原则-LSP
如果想用可替换的组件来构建软件系统,那么这些组件就必须遵守同一个约定,以便让这些组件可以相互替换。
0.1.4 接口隔离原则-ISP
我们这里的User1,User2,User3都是依赖OPS的,但是User1只需要用op1,User2用op2,User3用op3。在这种情况下,虽然User1不会和op2,op3产生直接的调用关系,但在源代码层次上也与他们形成依赖关系。这种依赖关系会导致两个问题:
-
修改op2,op3的逻辑会导致op1的逻辑变化
-
就算逻辑不变化,修改op2也会导致重新编译和部署User1
我们通过下面这种方将不同的操作隔离成接口,运用第5节的LSP,我们将OPS类实现这三个接口,然后替换在User1中的U1Ops,由于依赖的是最小接口所以就不会出现上面的问题。
0.1.5 依赖反转原则-DIP
高层策略性的代码不应该依赖实现底层细节的代码,相反,那些实现底层细节的代码应该依赖高层策略性的代码。
0.2 设计模式的分类
0.2.1 创建型设计模式
关注对象的创建机制。创建新对象可能导致项目复杂性增加,该类型的模式旨在通过控制创建过程来解决这种问题。
如:单例(Singleton)、工厂(Factory)、建造者(Builder)、原型(Prototype)、构造器(Contructor)
在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来,一个对象是通过克隆另一个对象所得到的。JavaScript就是属于原型对象编程。在JavaScript语言中,我们无需太注重很多创建型设计模式。
0.2.2 结构型设计模式
关注对象的组成与依赖关系。
如:代理(Proxy)、组合(Composite)、享元(Flyweight)、装饰器(Decorator)、适配器(Adapter)
0.2.3 行为设计模式
关注不同对象之间的通信。
如:观察者(Observer)、发布/订阅(Publish/Subscribe)、策略(Strategy)、迭代器(Iterator)、命令(Command)、模板方法(Tempalte)、职责链(Chain of Responsibility)、中介者(Mediator)、状态(State)
一、创建型设计模式
1.1 单例模式
定义
单例模式的核心是确保一个类只能有一个实例,即使多次实例化该类,也只返回第一次的实例化对象。单例模式不仅能减少不必要的内存开销, 并且在减少全局的函数和变量冲突也具有重要的意义。
ES6实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Singleton { constructor (name) { if (!Singleton.instance) { this.name = name Singleton.instance = this } return Singleton.instance } getName () { console.log(this.name) } } const a = new Singleton('第一次实例化') const b = new Singleton('第二次实例化') a.getName() //输出:第一次实例化 b.getName() //输出:第一次实例化 console.log(a===b) //输出:true
1.2 工厂模式
定义
把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
场景
假设有一个做汽车整车组装的代加工工厂“福是康”,它帮各大汽车品牌加工不同型号的汽车。我们看它可以帮哪些品牌生产哪些型号的汽车。
简单工厂模式实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
const CarFactory = function (brand) { function Product (options) { this.brand = options.brand this.models = options.models } switch (brand) { case 'BMW': return new Product ({ brand: '宝马', models: ['750', 'X5', 'Mini'] }) break; case 'benz': return new Product ({ brand: '奔驰', models: ['S600', 'GLS', 'smart'] }) break; case 'audi': return new Product({ brand: '奥迪', models: ['A8', 'Q7', 'R8'] }) default: throw new Error('参数错误,我们只生产:BMW/benz/audi') break; } } const BMWModels = new CarFactory('BMW') const benzModels = new CarFactory('benz') const audiModels = new CarFactory('audi')
工厂方法模式实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
const CarFactory = function (brand) { if (this instanceof CarFactory) { if (!this[brand]) throw new Error('参数错误,我们只生产:BMW/benz/audi') return new this[brand]() } else { return new CarFactory(brand) } } CarFactory.prototype = { BMW: function () { this.brand = '宝马', this.models = ['750', 'X5', 'Mini'] }, benz: function () { this.brand = '奔驰', this.models = ['S600', 'GLS', 'smart'] }, audi: function () { this.brand = '奥迪', this.models = ['A8', 'Q7', 'R8'] } } const BMWModels = CarFactory('BMW') const benzModels = CarFactory('benz') const audiModels = CarFactory('audi')
抽象工厂模式实现
二、结构型设计模式
三、行为设计模式
3.1 策略模式
定义
定义若干个算法,把它们各自封装成独立的策略方法,这些策略方法具有相同的目标和意图;当有计算需求时,将需求委托给某一个或多个策略方法去执行。
场景1
公司根据员工的当前工资和绩效打分来计算年终奖。例如,绩效为S的人年终奖是工资的4倍,绩效为A的人年终奖是工资的3倍。
原始代码实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var calculateBonus = function (performanceLevel, salary) { if (performanceLevel === 'S') return salary * 4 if (performanceLevel === 'A') return salary * 3 if (performanceLevel === 'B') return salary * 2 } calculateBonus('S', 2000) // 8000 calculateBonus('A', 1000) // 3000
策略模式实现-面向对象版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var performanceS = function () {} performanceS.prototype.calculate = function (salary) { return salary * 4 } var performanceA = function () {} performanceA.prototype.calculate = function (salary) { return salary * 3 } var performanceB = function () {} performanceB.prototype.calculate = function (salary) { return salary * 2 } var Bonus = function () { this.salary = null this.strategy = null } Bonus.prototype.setSalary = function (salary) { this.salary = salary } Bonus.prototype.setStrategy = function (strategy) { this.strategy = strategy } Bonus.prototype.get = function () { return this.strategy.calculate(this.salary) } // 应用 var bonus = new Bonus() bonus.setSalary(1000) bonus.setStrategy(new performanceS()) bonus.get() // 4000 bonus.setStrategy(new performanceA()) bonus.get() // 3000
策略模式实现-JavaScript版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// 普通版 var strategies = { "S": function (salary) { return salary * 4 }, "A": function (salary) { return salary * 3 }, "B": function (salary) { return salary * 2 } } var calculateBonus = function (level, salary) { return strategies[level](salary) } calculateBonus('S', 2000) // 8000 calculateBonus('A', 1000) // 3000 // 高阶函数版 var S = function (salary) { return salary * 4 } var A = function (salary) { return salary * 3 } var B = function (salary) { return salary * 2 } var calculateBonus = function (strategyFun, salary) { return strategyFun(salary) } calculateBonus(S, 1000) // 4000
场景2
给某个文本框添加多种校验规则
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 请输入用户名:<input type="text" name="userName" /> 请输入密码:<input type="text" name="password" /> 请输入手机号码:<input type="text" name="phoneNumber" /> <button>提交</button> </form> <script> /***********************策略对象**************************/ var strategies = { isNonEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.length < length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }; /***********************Validator 类**************************/ var Validator = function () { this.cache = []; }; Validator.prototype.add = function (dom, rules) { var self = this; for (var i = 0, rule; rule = rules[i++];) { (function (rule) { var strategyAry = rule.strategy.split(':'); var errorMsg = rule.errorMsg; self.cache.push(function () { var strategy = strategyAry.shift(); strategyAry.unshift(dom.value); strategyAry.push(errorMsg); return strategies[strategy].apply(dom, strategyAry); }); })(rule) } }; Validator.prototype.start = function () { for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) { var errorMsg = validatorFunc(); if (errorMsg) { return errorMsg; } } }; /***********************客户调用代码**************************/ var registerForm = document.getElementById('registerForm'); var validataFunc = function () { var validator = new Validator(); validator.add(registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用户名不能为空' }, { strategy: 'minLength:6', errorMsg: '用户名长度不能小于10 位' }]); validator.add(registerForm.password, [{ strategy: 'minLength:6', errorMsg: '密码长度不能小于6 位' }]); var errorMsg = validator.start(); return errorMsg; } registerForm.onsubmit = function () { var errorMsg = validataFunc(); if (errorMsg) { alert(errorMsg); return false; } }; </script> </body> </html>
3.2 状态模式
定义
把事物的每种状态封装成独立的类,跟此种状态有关的行为都封装在这个类的内部。将请求委托给当前的状态对象,当对象内部状态改变时,会带来不同的变化。
状态模式和策略模式比较
相同点
它们都有一些状态方法或者策略方法,都有一个上下文,上下文把请求委托给这些方法来执行。
不同点
策略模式中,各种策略类之间是平等又平行的,使用者必须熟知这些策略,然后通过参数自主地调用某个策略方法,甚至自由组合某些策略方法来产生作用。
状态模式中,各种状态之间的切换关系是内部定义好的,使用者只需触发某个状态即可,不需操心行为的编排。
场景
状态切换的场景非常多,比如切换某个dom元素的class,切换音视频播放状态。我们以切换电灯的开关为例,按一下打开弱光,再按一下打开强光,再按一下关灯。
原始代码实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <body> <script> var Light = function () { this.state = 'off' this.button = null } Light.prototype.init = function () { var button = document.createElement('button') var that = this button.innerHTML = '开关' this.button = document.body.appendChild(button) this.button.onclick = function () { that.buttonPressed() } } Light.prototype.buttonPressed = function () { if(this.state === 'off') { console.log('弱光') this.state = 'weakLight' } else if (this.state === 'weakLight') { console.log('强光') this.state = 'strongLight' } else if (this.state === 'strongLight') { console.log('关灯') this.state = 'off' } } var light = new Light() light.init() </script> </body> </html>
状态模式-面向对象版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <body> <script> // Light调度类 var Light = function () { this.offState = new OffState(this) this.weakLightState = new WeakLightState(this) this.strongLightState = new StrongLightState(this) this.button = null } // Light调度类的初始化方法 Light.prototype.init = function () { var button = document.createElement( 'button' ) var self = this this.button = document.body.appendChild( button ) this.button.innerHTML = '开关' this.currentState = this.offState this.button.onclick = function () { self.currentState.buttonPressed() } } // Light调度类设置当前状态的方法 Light.prototype.setState = function (newState) { this.currentState = newState } // 关闭状态类 var OffState = function (light) { this.light = light } OffState.prototype.buttonPressed = function () { console.log('弱光') this.light.setState(this.light.weakLightState) } // 弱光状态类 var WeakLightState = function (light) { this.light = light } WeakLightState.prototype.buttonPressed = function () { console.log('强光') this.light.setState(this.light.strongLightState) } // 强光状态类 var StrongLightState = function (light) { this.light = light } StrongLightState.prototype.buttonPressed = function () { console.log('关闭') this.light.setState(this.light.offState) } // 调用 var light = new Light() light.init() </script> </body> </html>
状态模式-JavaScript版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <body> <script> // 将客户的操作委托给delegation对象 var delegate = function (client, delegation) { return { buttonPressed: function () { return delegation.buttonPressed.apply(client, arguments) } } } // 状态机 var FSM = { off: { buttonPressed: function () { console.log('关灯') this.button.innerHTML = '下一次按会打开弱光' this.currentState = this.weakLightState } }, weak: { buttonPressed: function () { console.log('弱光') this.button.innerHTML = '下一次按会打开强光' this.currentState = this.strongLightState } }, strong: { buttonPressed: function () { console.log('强光') this.button.innerHTML = '下一次按会关灯' this.currentState = this.offState } } } // Light调度类 var Light = function () { this.offState = delegate(this, FSM.off) this.weakLightState = delegate(this, FSM.weak) this.strongLightState = delegate(this, FSM.strong) this.currentState = this.offState this.button = null } // Light调度类的初始化方法 Light.prototype.init = function () { var button = document.createElement( 'button' ) var self = this button.innerHTML = '已关灯' this.button = document.body.appendChild( button ) this.button.onclick = function () { self.currentState.buttonPressed() } } // 调用 var light = new Light() light.init() </script> </body> </html>
参考:
https://refactoringguru.cn/design-patterns/catalog