浅谈设计模式

Posted by Turingdo Studio on February 10, 2018

设计模式大部分都使用到了:两大原则

  • 功能单一原则
  • 开放-封闭原则
单体模式

什么是单体模式?单体模式是一个用来划分命名空间并将一批属性和方法组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。

单体模式的优点是:

  1. 可以用来划分命名空间,减少全局变量的数量。
  2. 使用单体模式可以使代码组织的更为一致,使代码容易阅读和维护。
  3. 可以被实例化,且实例化一次。
// 创建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";
};
代理模式
  • 例子一(代理人替墙洞给奶茶送戒指) ``` var MilkGirl = function (name) { this.name = name; }

//墙洞 var Ceo = function (girl) { this.girl = girl; //送结婚礼物,给奶茶 this.sendMarriageRing = function (ring) { console.log(“Hi “+ this.girl.name + “, ceo 送你一个礼物:”+ring) } }

//京东CEO的经纪人,来代替送

var ProxyMan = function (girl) { this.girl = girl; this.sendGift = function (gift) { //代理模式负责本体对象的实例化 (new Ceo(this.girl)).sendMarriageRing(gift) } };

//初始化 var proxy = new ProxyMan(new MilkGirl(“奶茶妹妹”));

proxy.sendGift(“戒指”)


* 例子二(加减法 缓存代理)

// 计算乘法 var mult = function () { var a = 1; for (var i = 0, ilen = arguments.length; i < ilen; i += 1) { a = a*arguments[i]; } return a; }; // 计算加法 var plus = function () { var a = 0; for (var i = 0, ilen = arguments.length; i < ilen; i += 1) { a += arguments[i]; } return a; } // 代理函数 var proxyFunc = function (fn) { var cache = {}; // 缓存对象 return function () { var args = Array.prototype.join.call(arguments, ‘,’); if (args in cache) { return cache[args]; // 使用缓存代理 } return cache[args] = fn.apply(this, arguments); } }; var proxyMult = proxyFunc(mult); console.log(proxyMult(1,2,3,4)); // 24 console.log(proxyMult(1,2,3,4)); // 缓存取 24 var proxyPlus = proxyFunc(plus); console.log(proxyPlus(1,2,3,4)); // 10 console.log(proxyPlus(1,2,3,4)); // 缓存取 10


* 虚拟代理合并http请求的理解

比如在做后端系统中,有表格数据,每一条数据前面有复选框按钮,当点击复选框按钮时候,需要获取该id后需要传递给给服务器发送ajax
请求,服务器端需要记录这条数据,去请求,如果我们每当点击一下向服务器发送一个http请求的话,对于服务器来说压力比较大,网络请求比较频繁,但是如果现在该系统的实时数据不是很高的话,我们可以通过一个代理函数收集一段时间内(比如说2-3秒)的所有id,一次性发ajax
请求给服务器,相对来说网络请求降低了, 服务器压力减少了;

// 首先html结构如下:
<p>
 <label>选择框</label>
 <input type="checkbox" class="j-input" data-id="1"/>
</p>
<p>
 <label>选择框</label>
 <input type="checkbox" class="j-input" data-id = "2"/>
</p>
<p>
 <label>选择框</label>
 <input type="checkbox" class="j-input" data-id="3"/>
</p>
<p>
 <label>选择框</label>
 <input type="checkbox" class="j-input" data-id = "4"/>
</p>

一般的情况下 JS如下编写

var checkboxs = document.getElementsByClassName(“j-input”); for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) { (function(i){ checkboxs[i].onclick = function(){ if(this.checked) { var id = this.getAttribute(“data-id”); // 如下是ajax请求 } } })(i); }


我们通过虚拟代理的方式,`延迟2秒,在2秒后获取所有被选中的复选框的按钮id,一次性给服务器发请求`。

代理方式

// 本体函数 var mainFunc = function (ids) { console.log(ids); // 即可打印被选中的所有的id // 再把所有的id一次性发ajax请求给服务器端 }; // 代理函数 通过代理函数获取所有的id 传给本体函数去执行 var proxyFunc = (function () { var cache = [], // 保存一段时间内的id timer = null; // 定时器 return function (checkboxs) { // 判断如果定时器有的话,不进行覆盖操作 if (timer) { return; } timer = setTimeout(function () {// 在2秒内获取所有被选中的id,通过属性isflag判断是否被选中 for (var i = 0, ilen = checkboxs.length; i < ilen; i++) { if (checkboxs[i].hasAttribute(“isflag”)) { var id = checkboxs[i].getAttribute(“data-id”); cache[cache.length] = id; } } mainFunc(cache.join(‘,’)); // 2秒后需要给本体函数传递所有的id // 清空定时器 clearTimeout(timer); timer = null; cache = []; }, 2000); } })(); var checkboxs = document.getElementsByClassName(“j-input”); for (var i = 0, ilen = checkboxs.length; i < ilen; i += 1) { (function (i) { checkboxs[i].onclick = function () { if (this.checked) { // 给当前增加一个属性 this.setAttribute(“isflag”, 1); } else { this.removeAttribute(‘isflag’); } // 调用代理函数 proxyFunc(checkboxs); } })(i); }


##### 模块模式

模块模式的思路是为`单体模式添加私有变量和私有方法能够减少全局变量的使用`;

var singleMode = (function () { // 创建私有变量 var privateNum = 112; // 创建私有函数 function privateFunc() { // 实现自己的业务逻辑代码 }

// 返回一个对象包含公有方法和属性
return {
    publicMethod1: publicMethod1,
    publicMethod2: publicMethod1
}; })(); ``` 模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,先定义了私有变量和函数,供内部函数使用,然后将一个对象字面量作为函数的值返 回,返回的对象字面量中只包含可以公开的属性和方法。这样的话,可以提供外部使用该方法;由于该返回对象中的公有方法是在匿名函数内部定义的, 因此它可以访问内部的私有变量和函数。

我们什么时候使用模块模式?

如果我们必须创建一个对象并以某些数据进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么我们这个时候就可以使用模块模式了。

策略模式

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

使用策略模式的优点如下:

优点:

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

传统面向对象的方式

var performanceA = function () {};
performanceA.prototype.calculate = function (salary) {
    return salary*4;
};
var performanceB = function () {};
performanceB.prototype.calculate = function (salary) {
    return salary*3;
};
var performanceC = function () {};
performanceC.prototype.calculate = function (salary) {
    return salary*2;
};
// 奖金类
var Bouns = function () {
    this.salary = null; // 原始工资
    this.levelObj = null; // 绩效等级对应的策略对象
};
Bouns.prototype.setSalary = function (salary) {
    this.salary = salary; // 保存员工的原始工资
};
Bouns.prototype.setlevelObj = function (levelObj) {
    this.levelObj = levelObj; // 设置员工绩效等级对应的策略对象
};
// 取得奖金数
Bouns.prototype.getBouns = function () {
    // 把计算奖金的操作委托给对应的策略对象
    return this.levelObj.calculate(this.salary);
};
var bouns = new Bouns();
bouns.setSalary(10000);
bouns.setlevelObj(new performanceA()); // 设置策略对象
console.log(bouns.getBouns()); // 40000

bouns.setlevelObj(new performanceB()); // 设置策略对象
console.log(bouns.getBouns()); // 30000

Javascript版本的策略模式

//代码如下:
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

表单验证

<form action = "http://www.baidu.com" id="registerForm" method = "post">
    <p>
     <label>请输入用户名:</label>
     <input type="text" name="userName"/>
    </p>
    <p>
     <label>请输入密码:</label>
     <input type="text" name="password"/>
    </p>
    <p>
     <label>请输入手机号码:</label>
     <input type="text" name="phoneNumber"/>
    </p>
</form>
var strategys = {
    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;
        }
    }
};
var Validator = function () {
    this.cache = []; // 保存效验规则
};
Validator.prototype.add = function (dom, rule, errorMsg) {
    var str = rule.split(":");
    this.cache.push(function () {
        // str 返回的是 minLength:6
        var strategy = str.shift();
        str.unshift(dom.value); // 把input的value添加进参数列表
        str.push(errorMsg); // 把errorMsg添加进参数列表
        return strategys[strategy].apply(dom, str);
    });
};
Validator.prototype.start = function () {
    for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
        var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
        if (msg) {
            return msg;
        }
    }
};
var validateFunc = function () {
    var validator = new Validator(); // 创建一个Validator对象
    /* 添加一些效验规则 */
    validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空');
    validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位');
    validator.add(registerForm.userName, 'mobileFormat', '手机号码格式不正确');
    var errorMsg = validator.start(); // 获得效验结果
    return errorMsg; // 返回效验结果
};
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function () {
    var errorMsg = validateFunc();
    if (errorMsg) {
        alert(errorMsg);
        return false;
    }
};

订阅发布者模式(也叫观察者模式)

如何实现发布–订阅模式?

  1. 首先要想好谁是发布者(比如上面的卖家)。
  2. 然后给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如下面的买家收藏了卖家的店铺,卖家通过收藏了该店铺的一个列表 名单)。
  3. 最后就是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

发布订阅模式的优点:

  1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。
  2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件 名,只要发布者的事件名不变,它不管发布者如何改变;

发布订阅模式的缺点:

创建订阅者需要消耗一定的时间和内存。 简单实现:

var shoeObj = {}; //定义发布者
// 缓存列表 存放订阅者回调函数
shoeObj.cache = []; //Careful!->这里定义的是个hash数组。

//增加订阅者
shoeObj.addListener = function (eventName,eventFn) {
    if(!this.cache[eventName]){
        //如果还没订阅过此类消息,给该类消息创建一个缓存列表
        this.cache[eventName] = [];
    }
    this.cache[eventName].push(eventFn)
}

//发布消息
shoeObj.publish = function () {
    var eventName = [].shift.call(arguments);//取出(第一个参数)消息类型
    var events = this.cache[eventName]; //取出该消息对应的回调函数集合
    //如果没有订阅过该消息,则返回
    if(!events || !events.length){
        return;
    }

    for(var i=0,event; event = events[i];i++){
        event.apply(this,arguments)
    }
}

//小明
shoeObj.addListener('red',function (size) {
    console.log("尺码是:"+size)
});
//小花
shoeObj.addListener("black",function (size) {
    console.log("尺码是:"+size)
})


shoeObj.publish("red",40);
shoeObj.publish("black",43);