前言
有关设计模式的学习资料中,大部分都是以 java 语言实现的,毕竟 java 作为老牌面向对象的语言最能说明设计模式的核心概念,所以 js 的相关设计模式的学习资料也大多使用 class 类实现,本文记录下 js 使用函数实现策略模式和状态模式设计模式的方式,更有助于理解策略模式和状态模式如何在实际工作中运用。
策略模式
定义一系列的算法,把它们一个一个封装起来,并且使它们可以相互替换。
目的:将算法的使用和算法实现分离开来
优点:
- 利用组合、委托、多态等思想,可以解决多重条件选择语句问题
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案
缺点:
- 代码会增加许多策略类和策略对象
- 需要全面了解各种 stragety, stragety要向客户暴露它的所有实现,违反最少知识原则
状态模式
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
优点:
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换
- 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支
- 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然
- Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响
缺点:
- 会在系统中定义许多状态类,而且系统中会因此而增加不少对象
性能优化点
-
- 1、仅当 state 对象被需要时才创建并随后销毁,用于节省内存,但不常变动的
- 2、一开始就创建好所有的状态对象,并且始终不销毁它们,用于状态经常变动的
- 由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑
相同点
- 都有一个上下文、一些策略或状态类
- 上下文把请求委托给这些类来执行
区别
状态模式
- 【不同事情 】状态模式各状态之间的切换,做不同的事情;
- 【不能互相替换 】状态模式各个状态的同一方法做的是不同的事,不能互相替换,状态和状态行为是早已被封封装好的,状态之间的切换也早被规定完成,改变模式这个行为发生在状态内部,使用者不需要了解改变的细节;
- 【封装状态】状态模式封装了对象的状态;
- 【状态不可重用】因为状态是跟对象密切相关的,它不能被重用;
- 【持有context 】在状态模式中,每个状态通过持有Context的引用,来实现状态转移;。
策略模式
- 【同样的事情】策略模式更侧重于根据具体情况选择策略,做同样的事情;
- 【可替换】策略模式各个策略之间是可替换的,平等又平行,互相之间没有任何联系,需熟知各个策略、各类的作用,以便随时切换算法;
- 【封装算法和策略】策略模式封装算法或策略;
- 【策略可重用】策略模式通过从Context中分离出策略或算法,我们可以重用它们;
- 【不持有context】但是每个策略都不持有Context的引用,它们只是被Context使用。context持有对某个策略对象的引用。
联系
状态模式和策略模式都是为具有多种可能情形设计的模式,把不同的处理情形抽象为一个相同的接口,符合对扩展开放,对修改封闭的原则。(优化多个条件判断语句的业务逻辑)
实践
策略模式
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
return salary * 2;
};
// 这里的 context 是 calculateBonus
var calculateBonus = function( func, salary ){
return func( salary );
};
calculateBonus( S, 10000 ); // 输出:40000
// 定义各个策略对象,每个策略对象都不持有 context 的引用,仅仅被 context 使用
const add = {
calculate: function (a, b) {
return a + b;
},
};
const subtract = {
calculate: function (a, b) {
return a - b;
},
};
const multiply = {
calculate: function (a, b) {
return a * b;
},
};
// 策略管理器,这里 Calculator 代表了 context,context 拥有执行不同算法的能力,传参为要执行的策略,context 持有对某个策略对象的引用
function Calculator(strategy) {
this.strategy = strategy;
this.setStrategy = function (newStrategy) {
this.strategy = newStrategy;
};
this.calculate = function (a, b) {
return this.strategy.calculate(a, b);
};
}
// 使用
// 传入或设置不同的策略,执行结果函数得到结果
const calculator = new Calculator(add);
console.log(calculator.calculate(2, 3)); // 5
calculator.setStrategy(subtract);
console.log(calculator.calculate(2, 3)); // -1
calculator.setStrategy(multiply);
console.log(calculator.calculate(2, 3)); // 6
状态模式
/ 状态对象
// 每个状态对象持有对传入状态的引用,以便流转至下一个状态
const redLight = {
name: '红灯',
nextState: function () {
return greenLight;
},
};
const yellowLight = {
name: '黄灯',
nextState: function () {
return redLight;
},
};
const greenLight = {
name: '绿灯',
nextState: function () {
return yellowLight;
},
};
// 状态管理器
function TrafficLightManager(initialState) {
this.state = initialState;
this.changeState = function () {
this.state = this.state.nextState();
};
}
// 使用
// 传入初始状态
const trafficLight = new TrafficLightManager(redLight);
console.log(trafficLight.state.name); // 红灯
// 通过状态流转方法切换下一个状态
trafficLight.changeState();
console.log(trafficLight.state.name); // 绿灯
trafficLight.changeState();
console.log(trafficLight.state.name); // 黄灯
// 封装执行状态的请求
var delegate = function (client, delegation) {
return {
buttonWasPressed: function () { // 将客户的操作委托给 delegation 对象
return delegation.buttonWasPressed.apply(client, arguments);
}
}
};
// 定义状态机
var FSM = {
off: {
buttonWasPressed: function () {
console.log('关灯');
this.button.innerHTML = '下一次按我是开灯';
this.currState = this.onState;
}
},
on: {
buttonWasPressed: function () {
console.log('开灯');
this.button.innerHTML = '下一次按我是关灯';
this.currState = this.offState;
}
}
};
// 这里 Light 代表了 context,context 持有各个状态对象的引用,根据状态的改变执行不同的行为
var Light = function () {
this.offState = delegate(this, FSM.off);
this.onState = delegate(this, FSM.on);
this.currState = this.offState; // 设置初始状态为关闭状态
this.button = null;
};
Light.prototype.init = function () {
var button = document.createElement('button'),
self = this;
button.innerHTML = '已关灯';
this.button = document.body.appendChild(button);
this.button.onclick = function () {
self.currState.buttonWasPressed();
}
};
var light = new Light()
本文大部分总结自书《javascript设计模式与开发实践》,仅供学习使用,如有错误,望大家多多指正。