设计模式简介
设计模式其实就是为了解决特定问题给出的简洁而优化的解决方案,是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
一、策略模式
策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换
场景1:
假如十一商城有促销,促销方案如下:
1、满100减5
2、满200减15
3、满300减30
4、满400减50
...后续可能会增加或者减少
function full100(price) {
return price - 5;
}
function full200(price) {
return price - 15;
}
function full300(price) {
return price - 30;
}
function full400(price) {
return price - 50;
}
// type折扣种类,price价格
function calculate (type, price) {
if (type == 'full100') {
return full100(price)
}
if (type == 'full200') {
return full200(price)
}
if (type == 'full300') {
return full300(price)
}
if (type == 'full400') {
return full400(price)
}
}
// 使用
const res = calculate('full100', 180)
从代码上看确实没啥毛病,但是如果情况变化,每添加一个方案就会写一个方法和一个if判断,所以这种方式扩展性不高。一般情况下,calculate只需要负责调用返回计算出的结果,对条件变化不做任何感知。
所以我们可以把它封装到一个对象中,每个算法都封装为一个方法,再写一个调用计算的方法给外部调用,然后只需要给它传参就行了。
const countPrice = {
returnPrice: {
full100: function (price) {
return price - 5
},
full200: function (price) {
return price - 15
},
full300: function (price) {
return price - 30
},
full400: function (price) {
return price - 50
}
},
getPirce: function (type, money) {
return this.returnPrice[type] ? this.returnPrice[type](money) : '没有优惠';
},
addRule: function (type, discount) {
this.returnPrice[type] = function (price) {
return price - discount;
}
}
}
// 计算
const res = countPrice.getPirce('full300',399)
// 添加规则
countPrice.addRule('full500', 100);
console.log(countPrice.getPirce('full500',599))
这样算法的实现和算法的使用就被分开了,并且便于扩展。
策略模式的通用实现
根据上面的例子提炼一下策略模式,折扣计算方式可以被认为是策略(Strategy),这些策略之间可以相互替代,而具体折扣的计算过程可以被认为是封装上下文(Context),封装上下文可以根据需要选择不同的策略。
主要有下面几个概念:
Context :封装上下文,根据需要调用需要的策略,屏蔽外界对策略的直接调用,只对外提供一个接口,根据需要调用对应的策略;
Strategy :策略,含有具体的算法,其方法的外观相同,因此可以互相代替;
StrategyMap :所有策略的合集,供封装上下文调用;
const StrategyMap = {}
function context(type, ...rest) {
return StrategyMap[type](...rest)
}
StrategyMap.minus100_30 = function(price) {
return price - Math.floor(price / 100) * 30
}
context('minus100_30', 270) // 输出: 210
场景2:
表单验证
我们在使用element-ui表单验证的时候,官网示例把表单验证都写在组件的状态 data 函数中,但是这样就不好复用使用频率比较高的表单验证方法。
所以我们可以结合策略模式改造下
// src/utils/validates.js
/* 姓名校验 由2-10位汉字组成 */
export function validateUsername(str) {
const reg = /^[\u4e00-\u9fa5]{2,10}$/
return reg.test(str)
}
/* 手机号校验 由以1开头的11位数字组成 */
export function validateMobile(str) {
const reg = /^1\d{10}$/
return reg.test(str)
}
/* 邮箱校验 */
export function validateEmail(str) {
const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
return reg.test(str)
}
// src/utils/index.js
import * as Validates from './validates.js'
/* 生成表格自定义校验函数 */
export const formValidateGene = (key, msg) => (rule, value, cb) => {
if (Validates[key](value)) {
cb()
} else {
cb(new Error(msg))
}
}
<script type='text/javascript'>
import * as Utils from '../utils'
export default {
name: 'ElTableDemo',
data() {
return {
rules: {
username: [{
validator: Utils.formValidateGene('validateUsername', '姓名由2-10位汉字组成'),
trigger: 'blur'
}],
mobile: [{
validator: Utils.formValidateGene('validateMobile', '手机号由以1开头的11位数字组成'),
trigger: 'blur'
}],
email: [{
validator: Utils.formValidateGene('validateEmail', '不是正确的邮箱格式'),
trigger: 'blur'
}]
}
}
}
}
</script>
实际项目中的使用:
策略模式的优缺点
策略模式将算法的实现和使用拆分,这个特点带来了很多优点:
策略之间相互独立,但策略可以自由切换,这个策略模式的特点给策略模式带来很多灵活性,也提高了策略的复用率;
如果不采用策略模式,那么在选策略时一般会采用多重的条件判断,采用策略模式可以避免多重条件判断,增加可维护性;
可扩展性好,策略可以很方便的进行扩展;
策略模式的缺点:
如果用户想采用什么策略,必须了解策略的实现,如果不去阅读这些算法,很容易走回以前的老套路或者重复封装。
二、适配器模式
是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配
适配器模式是一对相对简单的模式,直接上例子:
conlet st googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
let baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
let baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
let renderMap = function( map ){
if ( map.show instanceof Function ){
map.show();
}
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMapAdapter ); // 输出:开始渲染百度地图
// 适配成renderMap能使用
实际开发中:
项目中的table使用
核心就是解决两个已有接口之间不匹配的问题
三、装饰者模式
向一个现有的对象添加新的功能,同时又不改变其结构的设计模式称为装饰器模式
class Circle {
draw(){
console.log("画一个圆形");
}
}
class Decorator {
constructor(circle){
this.circle = circle;
}
draw(circle){
this.circle.draw();
this.setRedBorder(circle);
}
setRedBorder(circle){
console.log("设置红色边框");
}
}
//测试代码
let circle = new Circle();
circle.draw(); //画了一个圆形
//装饰器的作用
let decorator = new Decorator();
decorator.draw(circle); //画了一个圆形,并设置了红色边框
ES7装饰器
@Decorator
class Circle {
draw(){
console.log("画一个圆形");
}
}
function Decorator(target) {
const _draw = target.prototype.draw
target.prototype.draw= function(circle) {
_draw();
this.setRedBorder(circle);
}
target.prototype.setRedBorder = function(){
console.log("设置红色边框");
}
return target
}
//测试代码
let circle = new Circle();
circle.draw(); //画了一个圆形,并设置了红色边框