JS中的设计模式

在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。

在JavaScript中并没有类这种概念,JS中的函数属于一等对象,在JS中定义一个对象非常简单(var obj = {}),而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。

本文参照了《JavaScript 设计模式》一书,做了一些总结和个人理解。

设计原则

单一职责原则(SRP)

一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。应该把对象或方法划分成较小的粒度。

最少知识原则(LKP)

一个软件实体应当 尽可能少地与其他实体发生相互作用。
应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的 相互联系,可以转交给第三方进行处理。

开放-封闭原则(OCP)

软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定。

什么是设计模式

这里有个很好的比喻。

1
假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。

设计模式的原则是“找出 程序中变化的地方,并将变化封装起来”,它的关键是意图,而不是结构。

几种设计模式

构造器模式

  • 定义
    在面向对象编程中,构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。
    对象构造器是被用来创建特殊类型的对象的,首先它要准备使用的对象,其次在对象初次被创建时,通过接收参数,构造器要用来对成员的属性和方法进行赋值。
    Javascript不支持类的概念,但它有一种与对象一起工作的构造器函数。使用new关键字来调用该函数,我们可以告诉Javascript把这个函数当做一个构造器来用,它可以用自己所定义的成员来初始化一个对象。构造器内部,关键字this引用到刚被创建的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
function mataChuan( name, age, identity ) {

this.name = name;
this.age = age;
this.identity = identity;
}

mataChuan.prototype.bearingRight = function () {
return '你是不是个龙鸣';
};

// 我们可以示例化一个mataChuan
var mataChuan = new mataChuan( "mataChuan", 30, '日本新津天皇' );

单例模式

  • 定义
    保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function SetMataChuan(name) {
this.name = name;
}

SetMataChuan.prototype.getName = function() {
console.log(this.name);
};

// 提取出通用的单例
function getSingle(fn) {
var instance = null;
return function() {
if (!instance) {
instance = fn.apply(this, arguments);
}

return instance;
}
}

let SingleSetMataChuan = getSingle(function(name){
let mataChuan = new SetMataChuan(name);
return mataChuan.getName()
});

SingleSetMataChuan('mataChuan'); // mataChuan

工厂模式

  • 定义
    工厂模式是一种关注对象创建概念的创建模式。它的领域中同其它模式的不同之处在于它并没有明确要求我们使用一个构造器。取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var mataChuan  = function(){
this.nickName = '带带大师兄';
}

mataChuan.prototype = {
getGuoji :function(){
console.log('我是日本人,来中国留学好久了');
},
getSkill:function(){
console.log('-8000')
}
}

var Dingxiaoxiao = function(){
this.name = '丁潇潇';
}

Dingxiaoxiao.prototype = {
callMataChuan :function(){
console.log('mataChuan我们双流机场见一面');
},
getSkill:function(){
console.log('+8000')
}
}

var factory = function(type){
switch(type){
case 'Dingxiaoxiao': return new Dingxiaoxiao();
case 'mataChuan': return new mataChuan();
}
}

//然后我们就可以这么用
var mataChuan = factory('mataChuan');
var Dingxiaoxiao = factory('Dingxiaoxiao');
mataChuan.getGuoji();
Dingxiaoxiao.callMataChuan();
mataChuan.getSkill();
Dingxiaoxiao.getSkill();

策略模式

  • 定义
    定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,从而避免很多if语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let mataChuanSkills = {
"恰烂钱": function(money) {
return money * 2;
},
"网恋" : function(money) {
return money-8000;
},
"和深海哥打招呼" : function(money) {
return money * 20;
}
};
let mataChuanAction =function(type,money) {
return obj[type](money);
};
console.log(mataChuanAction('网恋',8000)); // 0

代理模式

  • 定义
    为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式主要有三种:保护代理、虚拟代理、缓存代理。

保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// 主体,发送消息
function sendMsg(msg) {
console.log(msg);
}

// 代理,对消息进行过滤
function proxySendMsg(msg) {
// 无消息则直接返回
if (typeof msg === 'undefined') {
console.log('好久不见深海哥');
return;
}

// 有消息则进行过滤
msg = msg.replace(/龙鸣给爷爬/g, '你好');

sendMsg(msg);
}


sendMsg('龙鸣给爷爬,XXX'); // 龙鸣给爷爬,XXX
proxySendMsg('龙鸣给爷爬,XXX'); // 你好,XXX
proxySendMsg(); // 好久不见深海哥
//在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体

缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 主体
function addGeng() {
var gengs = [].slice.call(arguments).join(',');
return gengs;
}

// 代理
var proxyAddGeng = (function() {
var oldGengs = '';
return function() {
var gengs = [].slice.call(arguments).join(',');
// 如果有,则直接从缓存返回
if (oldGengs.indexOf(gengs)>-1) {
console.log('已有梗',gengs)
return oldGengs;
} else {
oldGengs+=addGeng.apply(this, arguments);
return oldGengs;
}
};
})();

console.log(
proxyAddGeng('爬','龙鸣'),
proxyAddGeng('爬','龙鸣')
);

虚拟代理在控制对主体的访问时,加入了一些额外的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var mataChuan = function(name){
this.name = name;
}
//隐藏复杂,不愿意修改的的方法
var dogFans = function(mataChuan){
this.mataChuan = mataChuan;
this.send = function(fakeMsg){
console.log(fakeMsg+'@'+this.mataChuan.name);
}
}
var weibo = function(targetMan){
this.send = function(fakeMsg){
new dogFans(targetMan).send(fakeMsg);
}
}
var weibo = new weibo(new mataChuan("mata川"));
weibo.send("用激光笔照射坤坤的凶手找到了");
weibo.send("火烧明尼苏达烂尾楼的凶手找到了");

装饰者模式

  • 定义
    以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
    是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 装饰器,在当前函数执行前先执行另一个函数
    function decoratorBefore(fn, beforeFn) {
    return function() {
    var ret = beforeFn.apply(this, arguments);

    // 在前一个函数中判断,不需要执行当前函数
    if (ret !== false) {
    fn.apply(this, arguments);
    }
    };
    }


    function netKoi() {
    console.log('网恋');
    }

    function buyTicket() {
    console.log('买机票');
    }

    function driveCar() {
    console.log('开车');
    }

    function screwed(){
    console.log('被骗-8000');
    }

    var skillDecorator = decoratorBefore(screwed, buyTicket);
    skillDecorator = decoratorBefore(skillDecorator, driveCar);
    skillDecorator = decoratorBefore(skillDecorator, netKoi);

    skillDecorator(); // 跑步 音乐 数学

命令模式

  • 定义
    用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
    命名模式的目标是将方法的调用,请求或者操作封装到一个单独的对象中,给我们酌情执行同时参数化和传递方法调用的能力.另外,它使得我们能将对象从实现了行为的对象对这些行为的调用进行解耦,为我们带来了换出具体的对象这一更深程度的整体灵活性。
    实现明智简单的命令对象,将一个行为和对象对调用这个行为的需求都绑定到了一起.它们始终都包含一个执行操作(比如run()或者execute()).所有带有相同接口的命令对象能够被简单地根据需要调换,这被认为是命令模式的更大的好处之一。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var CarManager = {

// request information
requestInfo: function( model, id ){
return "The information for " + model + " with ID " + id + " is foobar";
},

// purchase the car
buyVehicle: function( model, id ){
return "You have successfully purchased Item " + id + ", a " + model;
},

// arrange a viewing
arrangeViewing: function( model, id ){
return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
}

};
CarManager.execute = function ( name ) {
return CarManager[name] && CarManager[name].apply( CarManager, [].slice.call(arguments, 1) );
};

CarManager.execute( "arrangeViewing", "Ferrari", "14523" );
觉得不错的话可以打赏哦