设计模式

设计模式

设计模式:针对特定问题的简洁而优雅的解决方案。

这些优秀的解决方案经过了大量实际项目的验证。

通俗一点说,设计模式就是给这些优秀的解决方案取个名字。

设计模式最初是静态类型语言中的设计模式,但设计模式实际上是解决某些问题的一种思想,与具体使用的语言无关。

类型

设计模式分为三种类型,共23种。
创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。

在许多大型Web项目中,JavaScript代码非常多,我们绝对有必要把一些优秀的设计模式借鉴到JavaScript这门语言中。

程序设计中的主要原则:

开闭原则,即 开放扩展,关闭修改
单一职责原则,指一个类或者模块应该有且只有一个改变的原因
高内聚低耦合,内聚性又称块内联系(模块内各元素联系的紧密性),耦合性也称块间联系(模块之间的独立性)

工厂模式

工厂模式是用来创建对象的一种最常用的设计模式。
在实际开发中,工厂模式主要用于复杂的对象构建、生成多个不同的实例对象等场景。

function factory(engine,speed,color) {
    var car = {}; //原料
    car.engine = engine; //加工
    car.speed = speed; //加工
    car.color = color; //加工
    car.drive = function () {
        console.log('最高时速为:' + car.speed);
    }
    return car; //出厂
}
var car1 = factory('v4','140km/h','blue');
var car2 = factory('v6','180km/h','red');
var factory = (function (){
    var car = {
        carA: function (){
            this.type = '高配版';
            this.engine = 'v8引擎';
            this.speed = '最高时速320km/h';
        },
        carB: function (){
            this.type = '中配版';
            this.engine = 'v6引擎';
            this.speed = '最高时速220km/h';
        },
        carC: function (){
            this.type = '低配版';
            this.engine = 'v4引擎';
            this.speed = '最高时速180km/h';
        }
    }
    return function (config){
        return new car[config]();
    }
})();
var car1 = factory('carA');
var car2 = factory('carB');
console.log(car1.type);
console.log(car2.type);

单例(体)模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用场景:对象仅需要创建一个的时候
1.windows的任务管理器
2.多线程的线程池设计
3.全局缓存
4.浏览器的window对象
5.页面中的登录弹窗,无论点击多少次,弹窗只会被创建一次

function Fun() {
    // 在函数中添加代码,使下面的运行结果成立
}
var obj1 = new Fun();
var obj2 = new Fun();
console.log(obj1 === obj2); //true
console.log(obj1.attr); // 'hello world'

最简单的单例--对象字面量

var instance = {
    prop1: '属性1',
    prop2: '属性2',
    doSomething: function (){
        console.log('do Something ...');
    }
}

使用闭包实现

var singleLogin = (function () {
    var instance;
    var CreateLogin = function () {
        if (instance) {
            return instance;
        }
        this.init();
        return instance = this;
    }
    CreateLogin.prototype.init = function () {
        var div = document.createElement('div');
        div.className = 'login';
        div.innerHTML = '登录窗口';
        document.body.appendChild(div);
    }
    return function () {
        return new CreateLogin();
    };
})();
console.log( singleLogin() === singleLogin() );

优化

var createLogin = function () { //创建对象
    var div = document.createElement('div');
    div.className = 'login';
    div.innerHTML = '登录窗口<span>X</span>';
    document.body.appendChild(div);
    return div;
}
var getSingle = function (fn) { //管理单例逻辑
    var result;
    return function () {
        // if (result) {
        //    return result;
        // }
        // return result = new fn();
        return result || ( result = new fn() );
    }
}
var singleLogin = getSingle(createLogin);
console.log( singleLogin() === singleLogin() ); //true

代理模式

代理模式是为一个对象提供一个代理,以便控制对它的访问。

代理模式分成两个部分,一个部分是本体,即为你想要实现的功能;另一部分为代理,代理可以代替本体做一些处理。

不用代理模式:

var Gift = function (n) {
    this.name = n;
};
var boy = {
    sendGift: function (target) {
        var flower = new Gift('花');
        target.receiveGift(flower);
    }
}
var girl = {
    receiveGift: function (gift) {
        console.log('收到礼物:' + gift.name);
    }
}
boy.sendGift(girl);

使用代理模式:

var Gift = function (n) {
    this.name = n;
};
var boy = {
    sendGift: function (target) {
        var flower = new Gift('花');
        target.receiveGift(flower);
    }
}
var guimi = {
    receiveGift: function (gift) {
        girl.receiveGift(gift);
    }
}
var girl = {
    receiveGift: function (gift) {
        console.log('收到礼物:' + gift.name);
    }
}
boy.sendGift(guimi);

在当前场景中,自己送花与找人代送没什么本质区别,代理模式确实没什么用!

我们来改变一下场景设置,假设 girl 收到花的时候,心情好时的成功几率是60%,心情不好时的成功几率为2.222%...

var Gift = function (n) {
    this.name = n;
};
var boy = {
    sendGift: function (target) {
        var flower = new Gift('花');
        target.receiveGift(flower);
    }
}
var guimi = { // 代理可以代替本体做一些处理
    receiveGift: function (gift) {
        // 假设 2 秒之后 girl 的心情变好
        setTimeout(function () {
            girl.receiveGift(gift);
        },2000);
    }
}
var girl = {
    receiveGift: function (gift) {
        console.log('收到礼物:' + gift.name);
    }
}
boy.sendGift(guimi);

普通预加载图片

var myImage = (function () {
        var imgNode = document.createElement('img');
        document.getElementById('loadImg').appendChild(imgNode);
        var img = new Image();
        img.onload = function () {
            imgNode.src = img.src;
        }
        return {
            setSrc: function (src) {
                imgNode.src = 'loading.gif';
                img.src = src;
            }
        }
})();
myImage.setSrc('http://pic39.photophoto.cn/20160411/1155116845138548_b.jpg');

上段代码中的 myImage 对象除了负责给 imgNode 设置 src 外,还要负责预加载图片,违背单一职责原则。
单一职责原则指的是,就一个类(也包括对象和函数等)而言,应该仅有一个引起它变化的原因。
假如进入5G时代不再需要预加载,我们不得不改动 MyImage 对象!
代理模式预加载图片

var imageModule = (function (){// 本体对象
  var showimg = new Image();
  return {
    setSrc: function (dom,src){
      showimg.src = src;
      dom.appendChild(showimg);
    }
  }
})();

var proxyModule = (function (){// 代理对象
  var loadimg = new Image();
  var showDom;
  loadimg.onload = function (){
    imageModule.setSrc(showDom,this.src);
  }
  return {
    setSrc: function (dom,src){
      showDom = dom;
      loadimg.src = src;
      imageModule.setSrc(dom,'./loading.gif');
    }
  }
})();
proxyModule.setSrc(box,'http://pic39.photophoto.cn/20160411/1155116845138548_b.jpg');

通过proxyModule间接地访问imageModule,代理对象proxyModule控制了客户对imageModule的访问,
并且在此过程中加入一些额外的操作,比如在真正的图片加载好之前,
先把showimg的src设置为一张本地的loading图片。

策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
一个基于策略模式的程序至少由两部分组成:
第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程;
第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某个策略类。
'算法':各种业务规则

demo1:计算奖金,基于传统面向对象语言的模仿

var performanceA = function () {}; // 策略类  
performanceA.prototype.calculate = function (salary) {
    return salary * 8;
}
var performanceB = function () {}; // 策略类
performanceB.prototype.calculate = function (salary) {
    return salary * 5;
}
var performanceC = function () {}; // 策略类
performanceC.prototype.calculate = function (salary) {
    return salary * 3;
}

var Bonus = function () { // 奖金类(环境类)
    this.salary = null; // 原始工资
    this.strategy = null; // 绩效等级对应的策略对象
}
Bonus.prototype.setSalary = function (salary) {
    this.salary = salary; // 设置原始工资
}
Bonus.prototype.setStrategy = function (strategy) {
    this.strategy = strategy; // 设置绩效等级对应的策略对象
}
Bonus.prototype.getBonus = function () { // 取得奖金数额
    // 把计算奖金的操作委托给对应的策略对象
    return this.strategy.calculate(this.salary);
}

var bonus = new Bonus(); // 奖金类实例
bonus.setSalary(10000); // 设置原始工资
bonus.setStrategy(new performanceA()); //设置策略对象
console.log(bonus.getBonus()); // 80000
bonus.setStrategy(new performanceB()); //设置策略对象
console.log(bonus.getBonus()); // 50000

demo2:JavaScript版本的策略模式

var strategies = { //策略对象,封装算法
    'A': function (salary) {
        return salary * 8;
    },
    'B': function (salary) {
        return salary * 5;
    },
    'C': function (salary) {
        return salary * 3;
    }
}
var getBonus = function (level, salary) { //环境类,接受客户的请求
    // 把计算奖金的操作委托给对应的策略对象
    return strategies[level](salary);
}
console.log(getBonus('A', 10000)); //80000
console.log(getBonus('B', 10000)); //50000

发布-订阅模式

发布-订阅模式(观察者模式),它定义对象间的一种 一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来替代传统的发布-订阅模式。最常用且最简单的发布-订阅模式:DOM事件

document.body.addEventListener('click', function () {
    alert('消息');
}, false);

订阅document.body上的click事件,当body节点被点击时,body节点便会向订阅者发布这个消息

// 发布订阅模式,是一种处理一对多的关系
function Observe(){
  this.cacheList = {};// 缓存列表
  // {
  //   'houseTypeA': [fn1,fn2,fn3],
  //   'houseTypeB': [fn1,fn2,fn3,fn4],
  //   'houseTypeC': [fn1,fn2]
  // }
}
Observe.prototype = {
  constructor: Observe,
  // 发布:取出对应的消息类型,执行所有回调函数传入发布的消息
  publish: function (type,message){
    if (!this.cacheList[type]){
      return;
    }
    this.cacheList[type].forEach(function (cb){
      cb(message);
    });
  },
  // 订阅:往某个消息类型中添加回调函数
  subscribe: function (type,callback){
    // 判断对象是否有type此属性
    if (!this.cacheList[type]) {
      this.cacheList[type] = [];
    }
    // 往对应的消息类型插入回调函数
    this.cacheList[type].push(callback);
  },
  // 取消订阅
  unsubscribe: function (type,callback){
    if (!type) {// 直接调用,没有任何参数
      this.cacheList = {};
    } else if (type&&!callback) {// 删除某个消息类型的所有回调函数
      delete this.cacheList[type];
    } else {// 删除某个消息类型中的某个回调函数
      this.cacheList[type] = this.cacheList[type].filter(function (cb){
        return cb !== callback;
      })
    }
  }
}
var obs = new Observe();

案例:网站登录
假如一个商城网站项目有header头部、nav导航、消息列表、购物车、地址管理等模块,这些模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。如果它们和用户信息模块产生了强耦合,比如下面这样的形式:

$.ajax({
    type: 'post',
    url: 'https://www.baidu.com/',
    data: 'user=xiaocuo&pass=123456',
    dataType: 'json',
    success: function (data) {
        header.setAvatar(data.avatar); // 设置头部头像
        nav.setAvatar(data.avatar); // 设置导航头像
        address.refresh(); // 刷新收货地址列表
        message.refresh(); // 刷新消息列表
        cart.refresh(); // 刷新购物车列表
        abc.refresh(data); // 刷新某某列表

        // ......
    }
})

我们必须知道header模块设置头像的方法叫setAvatar,刷新购物车列表的方法叫refresh,各种新增模块的方法...等等,我们会越来越疲于应付这些突如其来的业务要求!

用发布-订阅模式重构之后,对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件

var loginEvent = { //登录成功的消息事件
    clientList: {}, //缓存列表,存放订阅者的回调函数
    addlisten: function (key,fn) { //添加订阅者
        if (!this.clientList[key]) { //未订阅过此类消息,创建一个缓存列表
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn); //订阅的消息添加进消息缓存列表
    },
    trigger: function (key,msg) { //发布消息方法
        var fnArr = this.clientList[key]; //取出该消息对应的回调函数集合
        if (!fnArr || fnArr.length == 0) {
            return false; // 如果未订阅该消息,则返回
        }
        for (var i = 0; i < fnArr.length; i++) {
            fnArr[i](msg); //执行所有回调函数
        }
    }
}
 
$.ajax({
    type: 'post',
    url: 'https://www.baidu.com/',
    data: 'user=xiaocuo&pass=123456',
    dataType: 'json',
    success: function (data) {
        loginEvent.trigger('loginSucc', data); // 发布登录成功消息
    }
})

各个业务模块自己监听登录成功的消息:
var header = (function () { // 头部模块
    loginEvent.addlisten('loginSucc', function (data) { //订阅登录成功的消息
        header.setAvatar(data.avatar);
    });
    return {
        setAvatar: function (data) {
            console.log('设置头部模块头像');
        }
    }
})();
var nav = (function () { // 导航模块
    loginEvent.addlisten('loginSucc', function (data) { //订阅登录成功的消息
        nav.setAvatar(data.avatar);
    });
    return {
        setAvatar: function (data) {
            console.log('设置导航模块头像');
        }
    }
})();
var address = (function () { // 地址模块
    loginEvent.addlisten('loginSucc', function (obj) { //订阅登录成功的消息
        address.refresh(obj);
    });
    return {
        refresh: function (data) {
            console.log('刷新收货地址列表');
        }
    }
})();
// ......

Q.E.D.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值