JavaScript设计模式:策略模式、适配器模式、装饰者模式

设计模式简介

设计模式其实就是为了解决特定问题给出的简洁而优化的解决方案,是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

一、策略模式

策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换

场景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(); //画了一个圆形,并设置了红色边框

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值