javascript设计模式与六大原则

设计模式的目的

提高代码的重用性、可读性、可靠性、可维护性。

六大原则

  1. 单一职责原则
    • 理解:不同的类具备不同的职责,各司其职。做系统设计是,如果发现有一个类拥有了两种职责,那么就要问一个问题:可以将这个类分成两个类吗?如果真的有必要,那就分开,千万不要让一个类干的事情太多。
    • 总结:一个类只承担一个职责,一个对象只有一种引起他变化的原因。
  2. 里氏替换原则
    • 理解:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
    • 总结:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
  3. 依赖倒置原则
    • 理解:举例人吃苹果,我想吃苹果,但是我还想吃橘子,如果按照程序思维的话。就是三个类型,人Class,苹果Class,橘子Class,这种方式冗杂不好维护,不易理解,用水果来抽象化,苹果类继承并实现吃的动作。
    • 总结:使用接口或抽象类
  4. 接口隔离原则
    • 理解:如果一个类实现一个接口,但这个接口中有它不需要的方法,那么就需要把这个接口拆分,把它需要的方法提取出来,组成一个新的接口让这个类去实现
    • 总结:一个接口对实现它的类都是有用的。接口足够小
  5. 迪米特原则(最少知道原则)
    • 理解:一个对象应该对其他对象有最少的了解
    • 总结:类中只暴露不得不暴露的,其内部实现不暴露出去。
  6. 开闭原则
    • 理解:类、模块、函数,可以去扩展,但不要去修改。如果要修改代码,尽量用继承或组合的方式来扩展类的功能
    • 总结:是扩展不是修改

(1)单例模式

定义:一个类只有一个实例,并且提供一个访问它的全局访问点。

  • 最简单的单例模式无非是用一个变量来区分当前实例是否创建过,创建了返回之前的,没创建过创建。
    var singleton = function (name) { 
        this.name=name;
        this.instance=null;
    }
    singleton.prototype.getName=function(){
        alert(this.name)
    }
    singleton.getInstance=function(name){
        if(!this.instance){
            this.instance= singleton(name);
        }
        return this.instance;
    }
  • 惰性单例
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>遵循单一职责的单例模式</title>
</head>
<style>
    #loginBtn {
        width: 50px;
        height: 50px;
        background: #000;
    }
</style>

<body>
    <div id="loginBtn">

    </div>
</body>
<script>

    //负责创建登录浮窗对象
    var createLoginLayer = function () {
        var div = document.createElement('div');
        div.innerHTML = '我是登录浮窗';
        div.style.display = 'none';
        document.body.appendChild(div);
        return div;
    };
    //单例管理,确保这个实例创建并且只会创建一个
    var getSingle = function (fn) {
        var result;
        return function () {
            return result || (result = fn.apply(this, arguments));
        }
    };
    //需要的时候才初始化实例,并且只初始化一个
    document.getElementById('loginBtn').onclick = function () {
        var loginLayer = getSingle(createLoginLayer)();
        console.log(loginLayer)
        loginLayer.style.display = 'block';
    };
</script>

</html>

在这个例子中,遵循了单一职责原则。一个函数负责一种责任,getSingle负责单例状态的管理,createLoginLayer负责对象实例。

(2)策略模式

  • 定义有一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
  • 例子:计算年终奖

绩效为S 的人年终奖有4 倍工资,绩效为A 的人年终奖有3 倍工资,而绩效为B 的人年终奖是2 倍工资。

   var calculateBonus = function (level, salary) {
        if(level=== "S"){
            return salary*4;
        }
         if(level === 'A'){
            return salary*3;
        }
        if(level=== "B"){
            return salary*2;
        }
    }

这样一个简单的计算年终奖的函数就出现了。但是这样的函数不易于扩展,假设我们现在绩效为S的年终奖变为了3.5倍,等级变多了,就需要改动代码的源码。这样就违反了开闭原则。通过策略模式改版后的计算年终奖的方法如下代码所示:

var strategies = {
        "S": function (salary) {
            return salary * 4;
        },
        "A": function (salary) {
            return salary * 3;
        },
        "B": function (salary) {
            return salary * 2;
        }
    };
    var cacularBonus = function (level, salary) {
        return strategies[level](salary)
    }
    var level2=cacularBonus("A",200);

我们将计算年终奖的算法,放在一个对象内部,封装起来,在调用的时候可以通过不同的等级获得不同的计算方式。而我们有新的等级或者新的计算方式的时候,我们对该对象进行更改就可以了。避免了在一个函数内部进行计算,提高了可维护性。

(3)代理模式

  • 为一个对象提供一个代用品或占位符,以便控制对它的访问
  • 虚拟代理
    //真正的图片
   var myImage = (function () {
        var imgNode = document.createElement('img');
        document.body.appendChild(imgNode);
        return {
            setSrc: function (src) {
                imgNode.src = src;
            }
        }
    })();
    //图片代理
    var proxyImage = (function () {
        var img = new Image;
        img.onload = function () {
            myImage.setSrc(this.src);
        }
        return {
            setSrc: function (src) {
                myImage.setSrc('./loading.svg');
                img.src = src;
            }
        }
    })();
    proxyImage.setSrc('http://www.cdhrsip.com/static/imgs/high-tech/banner.png?version=201512141756');
  • 缓存代理
      var mult = function () {
            console.log('开始计算乘积');
            var a = 1;
            for (var i = 0, l = arguments.length; i < l; i++) {
                a = a * arguments[i];
            }
            return a;
        };
        var proxyMult = (function () {    
            var cache = {};
            return function () {
                var args = Array.prototype.join.call(arguments, ',');
                if (args in cache) {
                    return cache[args];
                }
                return cache[args] = mult.apply(this, arguments);
            }
        })();
       console.log( proxyMult(1, 2, 3, 4)); // 输出:24
       console.log(proxyMult(1, 2, 3, 4)); // 输出:24

通过增加缓存代理的方式,mult 函数可以继续专注于自身的职责——计算乘积,缓存的功能
是由代理对象实现

(4)迭代器模式

  • 供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
  • 迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种设计模式。目前
    的绝大部分语言都内置了迭代器。

(5)发布-订阅模式(观察者模式)

  • 它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
    现实举例:
  • :有这个几个人,小红、小强,小明和小龙,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼MM会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们。售楼处是发布者,这几个人是订阅者。
 var salesOffices = {};//售楼处
        salesOffices.clientList = {};//售楼处的客户列表,缓存列表,存放订阅者的回调函数
        salesOffices.listen = function (key, fn) { //如果没有订阅,添加到订阅列表
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn);//订阅的消息添加进消息缓存列表
        }
        salesOffices.trigger = function () { // 发布消息
        debugger
            var key = Array.prototype.shift.call(arguments), // 取出订阅消息
                fns = this.clientList[key]; // 取出该消息对应的回调函数集合
            if (!fns || fns.length === 0) { // 如果没有订阅该消息,则返回
                return false;
            }
            for (var i = 0, fn; fn = fns[i++];) {
                console.log(arguments)
                fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数
            }
        };
        salesOffices.listen('squareMeter88', function (price) { // 小明订阅88 平方米房子的消息
            console.log('价格= ' + price); // 输出: 2000000
        });
        salesOffices.listen('squareMeter110', function (price) { // 小红订阅110 平方米房子的消息
            console.log('价格= ' + price); // 输出: 3000000
        });
        salesOffices.trigger('squareMeter88', 2000000); // 发布88 平方米房子的价格
        salesOffices.trigger('squareMeter110', 3000000); // 发布110 平方米房子的价格

发布-订阅模式需要有几个元素:

  • 发布者(售楼处)
  • 发布者的缓存列表,用户存放用户订阅消息的回调(售楼部的花名册)
  • 发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)

(6)命令模式

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
命令模式分为三步:

  • 发送者: 不关心给哪个button,以及绑定什么事件,只要通过参数传入就好
 var setCommand=function(btn,fn){
          btn.onClick=function(){
              fn();
          }
      }
  • 执行命令者:需要接收到接受者的参数,当发送者发出命令时,执行就好
 var menu={
          refresh:function(){
              console.log("刷新")
          }
      }
  • 命令对象:不用关心在哪里被调用被谁调用,只需要按需执行就好了
   var commondObj=function(rev){
          return function(){
              rev.refresh();
          }
      }

(7)组合模式

  • 用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成
var closeDoorCommand = {
execute: function(){
console.log( '关门' );
}
};
var openPcCommand = {
execute: function(){
console.log( '开电脑' );
}
};
var openQQCommand = {
execute: function(){
console.log( '登录QQ' );
}
};
var MacroCommand = function(){
return {
commandsList: [],
add: function( command ){
this.commandsList.push( command );
},
execute: function(){
for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
command.execute();
}
}
}
};
var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();

这是一个组合模式,closeDoorCommand,openPcCommand,openQQCommand是子对象,MacroCommand是组合对象,组合模式的特点:

  1. 表示对象的部分-整体层次结构
  2. 客户希望统一对待树中的所有对象

(8)模板方法模式

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常
在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺
序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

  • coffice or tea
    泡咖啡步骤:1.把水煮沸2.用沸水冲泡咖啡3.把咖啡倒进杯子4.加糖和牛奶
    泡茶步骤:1.把水煮沸2.用沸水浸泡茶叶3.把茶水倒进杯子4.加柠檬
    抽象对象Beverage:1.把水煮沸2.用沸水泡3.把饮料倒进杯子4.加料
var Beverage = function(){};//咖啡和茶抽象为饮料
Beverage.prototype.boilWater = function(){//煮水
console.log( '把水煮沸' );
};
Beverage.prototype.brew = function(){}; // 空方法,应该由子类重写 泡
Beverage.prototype.pourInCup = function(){}; // 空方法,应该由子类重写 倒
Beverage.prototype.addCondiments = function(){}; // 空方法,应该由子类重写 加料
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};

咖啡子类和茶子类继承Beverage并实现它的方法,

var Coffee = function(){};
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){
console.log( '用沸水冲泡咖啡' );
};
var Tea=function(){}
Tea.prototype=new Beverage();
Tea.prototype.brew = function(){
console.log( '用沸水浸泡茶叶' );
};

在这一个继承重写的步骤中,Beverage.prototype.init这里的代码成为模板方法模式,描述清楚饮料的制作步骤。

(9)亨元模式

亨元模式是一种用于性能优化的模式,如果系统中由于创建了大量的类似的对象而导致内存占用率过高,亨元模式就有用了。

  • 给50个男模特和50个女模特,然后让他们每人分别穿上一件内衣来拍照。
var Model = function( sex, underwear){
this.sex = sex;
this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
var maleModel = new Model( 'male', 'underwear' + i );
maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
    var femaleModel= new Model( 'female', 'underwear' + j );
    femaleModel.takePhoto();
};

这种方式去处理问题,导致在内存中创建了100个差不多的对象。如果是1W个呢?性能上就会达到饱和。
运用亨元模式处理。

剥离外部状态

var Model = function( sex){
this.sex = sex;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
var maleModel = new Model( 'male' ),
femaleModel = new Model( 'female' );

for ( var i = 1; i <= 50; i++ ){
maleModel.underwear='underwear'+i;
maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
    femaleModel.underwear='underwear'+i;
   femaleModel.takePhoto();
};

运用了亨元模式之后,在内存中创建了两个对象就完成了所有的事情,故亨元模式的作用场景在:

  1. 一个程序中使用了大量的相似对象。
  2. 由于使用了大量对象,造成很大的内存开销。
  3. 对象的大多数状态都可以变为外部状态。
  4. 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

(10)职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间
的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

  • 售卖手机的电商网站

在正式购买后,已经支付过500 元定金的用
户会收到100 元的商城优惠券,200 元定金的用户可以收到50 元的优惠券,而之前没有支付定金
的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

 var order500 = function (orderType, pay, stock) {
            if (orderType == 1 && pay) {
                console.log('500 元定金预购, 得到100 优惠券');
            } else {
                order200(orderType, pay, stock);
            }
        }
        var order200 = function (orderType, pay, stock) {
            if (orderType == 2 && pay) {
                console.log('200 元定金预购, 得到50 优惠券');
            } else {
                orderNormal(orderType, pay, stock);
            }
        }
        var orderNormal = function (orderType, pay, stock) {
            if (stock > 0) {
                console.log('普通购买, 无优惠券');
            } else {
                console.log('库存不足');
            }
        }
        order500(1, true, 500); // 输出:500 元定金预购, 得到100 优惠券
        order500(1, false, 500); // 输出:普通购买, 无优惠券
        order500(2, true, 500); // 输出:200 元定金预购, 得到500 优惠券
        order500(3, false, 500); // 输出:普通购买, 无优惠券
        order500(3, false, 0); // 输出:手机库存不足

这种写法达到了职责链的方式方法,一次向下去查找合适的处理函数,但是加入我们加入添加一个300定金的呢?就需要去改动代码内部,违反了开闭原则,

  var order500 = function (orderType, pay, stock) {
           if (orderType == 1 && pay) {
               console.log('500 元定金预购, 得到100 优惠券');
           } else {
               return 'nextSuccessor';
           }
       }
       var order200 = function (orderType, pay, stock) {
           if (orderType == 2 && pay) {
               console.log('200 元定金预购, 得到50 优惠券');
           } else {
               return 'nextSuccessor';
           }
       }
       var orderNormal = function (orderType, pay, stock) {
           if (stock > 0) {
               console.log('普通购买, 无优惠券');
           } else {
               console.log('库存不足');
           }
       }
       var Chain = function (fn) {
           this.fn = fn;
           this.successor = null;
       };
       Chain.prototype.setNextSuccessor = function (successor) {
           return this.successor = successor;
       };
       Chain.prototype.passRequest = function () {
           var ret = this.fn.apply(this, arguments);
           if (ret === 'nextSuccessor') {
               return this.successor && this.successor.passRequest.apply(this.successor, arguments);
           }
           return ret;
       };
       var chainOrder500 = new Chain(order500);
       var chainOrder200 = new Chain(order200);
       var chainOrderNormal = new Chain(orderNormal);
       chainOrder500.setNextSuccessor( chainOrder200 );
       chainOrder200.setNextSuccessor( chainOrderNormal );
       chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
       chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
       chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
       chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

(11)中介者模式

中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应
该尽可能少地了解另外的对象(类似不和陌生人说话)。如果对象之间的耦合性太高,一个对象
发生改变之后,难免会影响到其他的对象,跟“城门失火,殃及池鱼”的道理是一样的。而在中
介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方。

(12)装饰者模式

给对象动态地增加职责的方式称为装饰者(decorator)模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

var plane = {
fire: function(){
console.log( '发射普通子弹' );
}
}
var missileDecorator = function(){
console.log( '发射导弹' );
}
var atomDecorator = function(){
console.log( '发射原子弹' );
}
var fire1 = plane.fire;
plane.fire = function(){
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2();
atomDecorator();
}
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹

(13)状态模式

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

var OffLightState = function( light ){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log( '弱光' ); // offLightState 对应的行为
this.light.setState( this.light.weakLightState ); // 切换状态到weakLightState
};
// WeakLightState:
var WeakLightState = function( light ){
this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function(){
console.log( '强光' ); // weakLightState 对应的行为
this.light.setState( this.light.strongLightState ); // 切换状态到strongLightState
};
// StrongLightState:
var StrongLightState = function( light ){
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
console.log( '关灯' ); // strongLightState 对应的行为
this.light.setState( this.light.offLightState ); // 切换状态到offLightState
};
var Light = function(){
this.offLightState = new OffLightState( this );
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置当前状态
this.button.onclick = function(){
self.currState.buttonWasPressed();
}
};
Light.prototype.setState = function( newState ){
this.currState = newState;
};

总结

拜读《javascript设计模式和开发实践》

设计模式就是对一系列业务或程序运行的封装,设计模式可以写出可维护性高, 运行效率高的代码,但每一种业务场景有不同的开发逻辑,不同的逻辑选用最适合的代码去编写,最终得到的才会是最好的,没有最优的设计模式,只有更好的设计模式,一个好的代码是经过多次的代码重构才能得到,才能算作对设计模式的完整遵循。了解前任给我们总结得出的设计模式,我们可以站在巨人的肩膀上,在自己写代码的时候,重构的时候,考虑代码的实现。是否合理?是否具备可扩展性?强壮性?等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值