浅谈设计模式 (一)

392 阅读4分钟

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。适用于所有面向对象的编程语言

设计原则 SOLID

单一职责原则(SRP)

一个对象/方法,只做一件事情。

SRP原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。 最明显的缺点是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。

开闭原则(OCP)

面向扩展开放,面向修改关闭,可扩展。

里氏置换原则(LSP)

子类能覆盖父类,父类能出现的地方子类就能出现,js中使用较少(弱类型&继承使用较少)

接口分离原则(ISP)

把大的接口拆分成小的接口(功能单一)

依赖倒置原则(DIP)

面向接口编程,依赖于抽象而不依赖具体,使用方只关注接口而不关注具体类的实现

js 设计模式

单例模式

核心思想是确保一个类只对应一个实例,并且对外暴露一个全局访问点。

js是弱类型语言,也有自己的构造函数和相应的实例,单例模式下,需要确保多次调用构造函数返回的都是同一个实例,也就是在实例化的时候先检查实例是否存在,有则直接返回,无则实例化后再返回。

var Singleton = function(name) {
  this.name = name;
};
Singleton.prototype.getName = function() {
  return this.name;
};
// 获取实例对象
function getInstance(name) {
  if (!this.instance) {
    this.instance = new Singleton(name);
  }
  return this.instance;
}
// 测试单体模式的实例
var a = getInstance("aa");
var b = getInstance("bb");

单例模式只能实例化一次,所以上面的a和b都是同一个实例,
a.getName() //aa
b.getName() //aa

闭包实现单例模式

// 单体模式
var CreateDiv = function(html) {
    this.html = html;
    this.init();
}
CreateDiv.prototype.init = function(){
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
};
// 代理实现单体模式
var ProxyMode = (function(){
    var instance;
    return function(html) {
        if(!instance) {
            instance = new CreateDiv("我来测试下");
        }
        return instance;
    } 
})();
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
console.log(a===b);// true

单例模式实现弹窗 例子

// 创建div
var createWindow = function(){
    var div = document.createElement("div");
    div.innerHTML = "我是弹窗内容";
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
};
// 创建iframe
var createIframe = function(){
    var iframe = document.createElement("iframe");
    document.body.appendChild(iframe);
    return iframe;
};
// 获取实例的封装代码
var getInstance = function(fn) {
    var result;
    return function(){
        return result || (result = fn.call(this,arguments));
    }
};
// 测试创建div
var createSingleDiv = getInstance(createWindow);
document.getElementById("Id").onclick = function(){
    var win = createSingleDiv();
    win.style.display = "block";
};
// 测试创建iframe
var createSingleIframe = getInstance(createIframe);
document.getElementById("Id").onclick = function(){
    var win = createSingleIframe();
    win.src = "http://cnblogs.com";
};

在Vue实现单例模式

先写一个单文件弹窗组件--demo.js,由于时间关系就写一个基本的div作为弹窗,组件接受一个变量 isShow 控制弹窗是否显示。

<template>
  <div id="#demo" class="demo" v-show="isShow">
    登录弹窗demo
  </div>
</template>

<script>

export default {
  name: "demo",
  props: {
    isShow: {
      type: Boolean,
      default: false
    }
  }
};
</script>

然后,在全局引用并挂载到vue的原型链上,

Vue.extend() 创建一个根据模板生成子类的构造器,data必须是函数

import demo from "@/components/global/demo";
import Vue from "vue";
//根据写好的组件创建构造器
const Con = Vue.extend(demo);
// 创建 ins 实例
const ins = new Con();

class LoginModule {
  show() {
    //判断实例是否存在
    if (ins.$el) {
      //存在修改实例中的显示变量
      ins.isShow = true;
    } else {
      //不存在,先手动挂载实例,再将实例追加到body里面
      ins.$mount(document.createElement("div"));
      document.body.appendChild(ins.$el);
    }
  }
  hide() {
    ins.isShow = false;
  }
}

let install = {
  //install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
  install(Vue) {
    window.$LoginModule = Vue.prototype.$LoginModule = new LoginModule();
  }
};
//调用 install.install 
Vue.use(install);

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

策略模式指的是定义一系列的算法,把它们一个个封装起来,将不变的部分和变化的部分隔开,实际就是将算法的使用和实现分离出来;算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数,而算法的实现是根据绩效对应不同的绩效规则;

一个基于策略模式的程序至少由2部分组成,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,该Context接收客户端的请求,随后把请求委托给某一个策略类。

//代码如下:
var obj = {
        "A": function(salary) {
            return salary * 4;
        },
        "B" : function(salary) {
            return salary * 3;
        },
        "C" : function(salary) {
            return salary * 2;
        } 
};
var calculateBouns =function(level,salary) {
    return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000

优点:

  1. 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。
  2. 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。
  3. 策略模式中的代码可以复用。

用策略模式编写表单验证功能: 原本的验证代码

var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    if(registerForm.userName.value === '') {
        alert('用户名不能为空');
        return;
    }
    if(registerForm.password.value.length < 6) {
        alert("密码的长度不能小于6位");
        return;
    }
    if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
        alert("手机号码格式不正确");
        return;
    }
}

第一步 封装策略对象

var strategy = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    // 限制最小长度
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    // 手机号码格式
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    } 
};

第二步 实现context类 接收用户请求并委托给策略对象

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 strategys[strategy].apply(dom,strategyAry);
            });
        })(rule);
    }
};
Validator.prototype.start = function(){
    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
    var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
    if(msg) {
        return msg;
    }
    }
};

第三步 调用代码

// 代码调用
var registerForm = document.getElementById("registerForm");
var validateFunc = function(){
    var validator = new Validator(); // 创建一个Validator对象
    /* 添加一些效验规则 */
    validator.add(registerForm.userName,[
        {strategy: 'isNotEmpty',errorMsg:'用户名不能为空'},
        {strategy: 'minLength:6',errorMsg:'用户名长度不能小于6位'}
    ]);
    validator.add(registerForm.password,[
        {strategy: 'minLength:6',errorMsg:'密码长度不能小于6位'},
    ]);
    validator.add(registerForm.phoneNumber,[
        {strategy: 'mobileFormat',errorMsg:'手机号格式不正确'},
    ]);
    var errorMsg = validator.start(); // 获得效验结果
    return errorMsg; // 返回效验结果
};
// 点击确定提交
registerForm.onsubmit = function(){
    var errorMsg = validateFunc();
    if(errorMsg){
        alert(errorMsg);
        return false;
    }
}

使用闭包隐藏算法,并开放一个拓展入口

const PriceCalculate = (function() {	
    /* 售价计算方式 */	
    const DiscountMap = {	
        minus100_30: function(price) {      // 满100减30	
            return price - Math.floor(price / 100) * 30	
        },	
        minus200_80: function(price) {      // 满200减80	
            return price - Math.floor(price / 200) * 80	
        },	
        percent80: function(price) {        // 8折	
            return price * 0.8	
        }	
    }	
    return {	
        priceClac: function(discountType, price) {	
            return DiscountMap[discountType] &amp;&amp; DiscountMap[discountType](price)	
        },	
        addStrategy: function(discountType, fn) {        // 注册新计算方式	
            if (DiscountMap[discountType]) return	
            DiscountMap[discountType] = fn	
        }	
    }	
})()	
PriceCalculate.priceClac('minus100_30', 270)    // 输出: 210	
PriceCalculate.addStrategy('minus150_40', function(price) {	
    return price - Math.floor(price / 150) * 40	
})	
PriceCalculate.priceClac('minus150_40', 270)