JavaScript 学习笔记(三)设计模式

设计模式:是实战设计经验的总结。能够让我们,站在巨人的肩膀上,构建项目代码。

设计原则:

  • 开闭原则:对扩展开放,对修改关闭。(PS:高考试卷)
  • 里氏代换原则:子类继承父类,单独调用可以独立运行。(PS :盗版光盘。)
  • 依赖倒转原则:引用一个对象,如果这个对象有底层类,直接引用底层。(PS:三个和尚抬水,也可以直接从井里打水)
  • 接口隔离原则:每一个接口应该是一种角色(PS:汽车点烟器接口)
  • 合成/聚合复用原则:新对象应使用一些已有的对象,使之成为新对象的一部分。(PS:利用手里有一些相机零件,制造新相机)
  • 迪米特原则:一个对象应该对其他对象有尽可能少的了解。(PS:现实种男女的对象)

工厂模式

概念:定义一个用于创建对象的接口,由子类决定设立化哪个类。类的实例化延迟到了子类。而子类可以重写接口方法,以便创建的时候指定自己的对象类型(抽象工厂)

常用举例:尤其是创建对象流程赋值时,如依赖于很多设置文件等。

作用:

  • 对象的构建十分复杂。
  • 需要依赖具体的环境创建不同的实例
  • 处理大量具有相同属性的小对象

先复习一下,JS 的继承写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'use strict'
function Animal (name){
this.name = name;
this.sleep = function(){
console.log(`${name}, is sleeping!`);
}
}
Animal.prototype.eat = function(food){
console.log(`${this.name}, eats ${food}`);
};
function Cat(){
var name = [].shift.call(arguments);
Animal.call(this,name);
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var c = new Cat("cat");
c.eat("fish");
console.log(`c instanceof Animal is ${c instanceof Animal}`);
console.log(`c instanceof Cat is ${c instanceof Cat}`);

简单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict'
function XMLHttpFactory(){};
XMLHttpFactory.createXMLHttp = function(){
var XMLHttp = null;
if(window.XMLHttpRequest){
XMLHttp = new XMLHttpRequest();
}else if(window.ActiveXObject('Microsoft.XMLHTTP')){
XMLHttp = new ActiveXObject('Microsoft.XMLHTTP');
}
return XMLHttp;
};
var AjaxHandler = function(){
var XMLHttp = XMLHttpFactory.createXMLHttp();
console.log(XMLHttp);
};
AjaxHandler();

抽象工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function XMLHttpFactory(){};
XMLHttpFactory.prototype = {
createXMLHttp:function(){
throw new Error("This is an absttact calss!");
}
};
function AjaxHandler(){
XMLHttpFactory.call(this);
};
AjaxHandler.prototype = new XMLHttpFactory();
AjaxHandler.prototype.createXMLHttp = function(){
var XMLHttp = null;
if(window.XMLHttpRequest){
XMLHttp = new XMLHttpRequest();
}else if(window.ActiveXObject('Microsoft.XMLHTTP')){
XMLHttp = new ActiveXObject('Microsoft.XMLHTTP');
}
return XMLHttp;
}
AjaxHandler.prototype.constructor = AjaxHandler;
var handler = new AjaxHandler();
var foo = handler.createXMLHttp();
console.log(handler instanceof XMLHttpFactory);//true

单例模式

作用

  • 模块间通信
  • 系统某个类的实例只能存在一个
  • 保护自己的属性和方法

注意

  • 闭包是 变量作用域的链式提升
    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
    'use strict'
    //1.独立对象 xiaowang ,xiaoli
    //2.让xiaowang和xiaoli 通过门铃通信
    //3.先看xiaowang家有没有门,无门建门,然后通过门铃“叮咚”
    $(document).ready(function(){
    var xiaowang = (function(){
    var men = null;
    var info = {
    receiveMessage : function(msg){
    if(!men){
    men = {};
    }
    men.menling = msg;
    console.log(`xiaowang men.menling : ${men.menling} ` );
    return men.menling;
    }
    };
    return info;
    })();
    var xiaoli = {
    callXiaowang:function(msg){
    xiaowang.receiveMessage(msg);
    }
    };
    xiaoli.callXiaowang("叮咚");
    });
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
41
42
43
44
45
46
47
48
49
50
'use strict'
//1.页面上有个button
//2.使用top 和banner 分成2组实现命名空间
//3.top和banner进行通信
$(document).ready(function(){
var top = {
a:1,
init:function(){
this.render();
this.bind();
},
render:function(){
var me = this;
me.btnA = $("#btn_a");
me.btnB = $("#btn_b");
},
bind:function(){
var me = this;
me.btnA.click(function(){
me.test(me.btnA);
});
},
test:function(domObj){
console.info(domObj.attr("id"));
}
};
var banner = {
a:1,
init:function(){
this.render();
this.bind();
},
render:function(){
var me = this;
me.btnE $("#btn_E);
me.btnF= $("#btn_F);
},
bind:function(){
var me = this;
me.btnA.click(function(){
me.test(me.btnA);
});
},
test:function(domObj){
top.a = 100;//模块之间通信
}
};
top.init();
banner.init();
});

构造函数模式

如下例,常和单例模式配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'use strict'
$(document).ready(function(){
var AA = {
DoorFactory :function(para){
this.figure = para.figure || "普通";
this.lock = para.lock || "B级";
this.crateDoor = function(){
return `锁:${this.lock}, 花纹:${this.figure}`;
}
}
};
var xiaozhang = new AA.DoorFactory({figure:"古典",lock:"C级"});
console.info(xiaozhang.crateDoor());
var xiaoliu = new AA.DoorFactory({figure:"欧式",lock:"E级"});
console.info(xiaoliu.crateDoor());
});

代理模式

为其他对象提供一种代理以控制对这个对象的访问。
作用

  • 远程代理
  • 虚拟代理
  • 安全代理
  • 智能代理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    'use strict'
    $(document).ready(function(){
    var HouseOwner = function(buyer){
    this.buyer = buyer;
    this.sellHouse = function(money){
    console.info(`买家:${this.buyer.name} ,房价:${money}`);
    }
    };
    var Agency = function(){};
    Agency.prototype.trade = function() {
    var ho = new HouseOwner(new Buyer('小明'));
    ho.sellHouse('200万')
    };
    var Buyer = function(name){
    this.name = name || "anonym"
    };
    new Agency().trade();
    });

建造者模式

用途:

  • 分步创建一个复杂的对象
  • 解耦封装过程和具体创建的组件
  • 无需关系组件如何组装
    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
    'use strict'
    $(document).ready(function(){
    function House(){
    this.kitchen = "";
    this.bedroom = "";
    this.livingroom ="";
    }
    function Headman(){
    this.buildHouse = function(worker){
    worker.buildbedroom();
    worker.buildKitchen();
    worker.buildLivingroom();
    var house = new House();
    house.kitchen = "finish";
    house.bedroom = "finish";
    house.livingroom = "finish";
    return house;
    }
    }
    function Worker(){
    this.buildKitchen = function(){
    console.info("kitchen ok!");
    }
    this.buildbedroom = function(){
    console.info("bedroom ok!");
    }
    this.buildLivingroom = function(){
    console.info("livingroom ok!");
    }
    }
    var headman = new Headman();
    var house = headman.buildHouse(new Worker());
    console.log(house);
    });

命令模式

作用:

  • 将函数的封装、请求、调用结合为一体。
  • 调用具体函数解耦命令对象雨接收对象。
  • 提高程序模块化的灵活性。

注意:

  • 不需要接口一致,直接调用函数即可,以免浪费。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'use strict'
$(document).ready(function(){
var lian = {};
lian.paobing = function(num){
console.info(`${num}炮兵,参加战斗`);
}
lian.bubing = function(num){
console.info(`${num}步兵,参加战斗`);
}
lian.lianzhang = function(para_mingling){
lian[para_mingling.type](para_mingling.num);
}
//总司令下命令
lian.lianzhang({
type:'paobing',
num:100
});
lian.lianzhang({
type:'bubing',
num:1000
});
});

观察者模式

(又名发布订阅模式 publish / subscrib)
定义了一种一对多的关系,让多个观察者对象同时监听某一主题对象,这个主题对象的状态发生变化时,就会通知所有的观察者对象,使他们能够自己更新自己。

作用

  • 支持简单的广播通信,自动通知所有已经订阅过的对象。
  • 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  • 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

版本一

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
41
42
43
44
45
46
47
48
49
50
51
52
53
var pubsub = {};
(function(ps){
var topics = {};
var token = 0;
ps.subscribe = function(topic,fn ){
if(!topics[topic]){
topics[topic] = [];
}
topics[topic].push({
token: ++token,
fun:fn
});
return token;
}
ps.unSubscribe = function(token){
for(topic in topics){
var subs = topics[topic];
if(subs instanceof Array ){
for(let i=0;i<subs.length;i++){
var sub = subs[i];
if(sub.token === token){
subs.splice(i,1);
console.info("unSubscribe: " + sub.token);
}
}
}
}
}
ps.publish = function(topic,args){
if(!topics[topic]){
return false;
}
//有些会把下面这个循环放入setTimeout执行
for(let subscriber of topics[topic]){
subscriber.fun(args);
}
}
})(pubsub);
var diameteSbToken = pubsub.subscribe('radias',(r)=>{
console.log("直径: " + 2 * r);
})
pubsub.subscribe('radias',function(r){
console.log("圆周: " + 2 * Math.PI * r);
})
pubsub.subscribe('radias',function(r){
console.log("圆面积: " + Math.PI * r * r);
})
pubsub.publish('radias',2);
pubsub.publish('radias',3);
pubsub.unSubscribe(diameteSbToken);//取消订阅
pubsub.publish('radias',5);
console.log("end");

根据jQuery1.7版新增的on/off功能,我们也可以定义jQuery版的观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function ($) {
var o = $({});
$.subscribe = function () {
o.on.apply(o, arguments);
};
$.unsubscribe = function () {
o.off.apply(o, arguments);
};
$.publish = function () {
o.trigger.apply(o, arguments);
};
} (jQuery));
var diameter = function(e,r) {
console.log("直径: " + 2 * r);
}
$.subscribe('radias',diameter);
$.subscribe('radias',function(e,r){
console.log("圆周: " + 2 * Math.PI * r);
})
$.publish('radias',100);
$.unsubscribe("radias");
$.publish('radias',100); //退订整个topic
console.log("end");

总结

  • 观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

  • 总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。

参考资料

适配器模式

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
`use strict`
/*
原有对象 aa
var aa = {
test:function(arg){
//do something
},
go:function(){
//do something
}
};
*/
//被重构成pp,但功能美有改变
function PP(){
this.boo = function(){
console.info('boo');
}
this.foo = function(){
console.info('foo')
}
}
//
function adapter(){
var p = new PP();
var aa = {
test : p.boo,
go: p.foo
}
return aa;
}
var aa = adapter();
aa.test();
aa.go();

职责链模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
`use strict`
//职责链模式
var start = new Date();
function Boss(manager){
this.manager = manager;
}
Boss.prototype.write = function(codeType) {
this.manager.write(codeType);
}
function Manager(coder){
this.coder = coder;
}
Manager.prototype.write = function(codeType) {
this.coder.write(codeType);
};
function Coder(){};
Coder.prototype.write = function(codeType) {
console.info(`i am writing ${codeType}`);
};
new Boss(new Manager(new Coder())).write('javaScript');

迭代器模式

作用:

  • 为遍历不同的集合结构提供一个统一的接口。从而支持同样的算法,在不同的集合结构上进行操作
  • 对集合内部结果常常变化各异,我们不想暴露其内部结构的化,但又想让客户代码透明访问其中的元素。

注:

  • 一般迭代,至少要又两个方法 hasNext(),next(),这样才可以遍历所有对象
  • 遍历的同时更改迭代器所在的集合结构可能会导致问题(比如c# 的foreach 里不允许修改item)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
`use strict`
//迭代器模式
var arr = ["1","2","3"];
var iterator = (function(){
var length = arr.length,
index = 0;
return {
hasNext : function(){
return index < length;
},
next:function (){
var data = arr[index];
index++;
return data;
}
}
})();
while(iterator.hasNext()){
console.info(it.next());
}

外观模式

又称:Facade (门面模式)。
作用

  • 在设计初期,应该要有意识地将不同地两个层分离,比如经典地三层结构
  • 在开发阶段,子系统往往因为不断地重构演化而变得越来越复杂,增加外观Facade,可以提供一个简单的接口,减少他们之间的依赖。
  • 在维护一个遗留的大型系统时,为系统开发一个Facade类,为设计粗糙和高度复杂的遗留代码提供较清晰的接口,让新系统和Facade交互。

注意

  • 外观模式被连续开发时,会产生一定的性能问题,因为在每次调用时,都要检测功能的可行性。
    例1:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    `use strict`
    var fuhao = {};
    fuhao.huofang = function(){
    return "馒头";
    };
    fuhao.chuliliangshi = function(){
    return "面粉";
    };
    fuhao.mantou = function(){
    this.chuliliangshi();
    this.huofang();
    }
    //人们想拿到馒头,第一个需要做的就是让系统产生馒头
    fuhao.facade = function(){
    return this.mantou();
    };

例2:

1
2
3
4
5
6
7
8
var stopEvent = function(e){
e.stopPropagation();
e.preventDefault();
}
//stopEvent 就是生产门面的
$('#a').click(function(){
stopEvent(e);
});

策略模式

作用:

  • 所有的这些算法都是做相同的事情,只是实现不同。
  • 以相同的方式调用所有的方法,减少了各种算法与使用算法之间的耦合
  • 单独定义算法类,也方便了单元测试
    注意:
  • 不仅可以用来封装算法,也可以用来封装几乎任何类型的规则。
    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
    `use strict`
    $(document).ready(function(){
    var input = $("#ipt_tel");
    $.fn.validate = function(option){
    var ret = true;
    for(key in option){
    if('isEmpty' === key){
    console.log("isEmpty = " + isEmpty());
    ret = (isEmpty() === option[key]) && ret;
    }
    if('isNum' === key){
    console.log("isNum " + isNum() );
    ret =(isNum() === option[key]) && ret;
    }
    }
    function isEmpty(){
    var ret = false;
    if(!input.val()){
    ret = true;
    }
    return ret;
    }
    function isNum(){
    var reg = /[0-9]+/;
    return reg.test(input.val());
    }
    return ret;
    }
    $('#btn_a').click(function(e){
    var result = input.validate({
    isEmpty:false,
    isNum : true
    });
    console.info(result);
    });
    });

中介者模式

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以对立的改变他们之间的交互。

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
`use strict`
//中介者模式
function Plane(name){
this.name = name;
}
Plane.prototype.send = function(msg,to) {
tatai.send(msg,to);
console.info(`${this.name} 发送消息 : ${msg} 到 : ${to.name}`);
};
Plane.prototype.receive = function(msg) {
console.info(`${this.name} 收到: ${msg}`);
};
var tatai = {
all:{},
regist:function(plane){
this.all[plane.name] = plane;
},
send:function(msg,to){
this.all[to.name].receive(msg);
}
}
var p1 = new Plane('Plane 1');
var p2 = new Plane('Plane 2');
tatai.regist(p1);
tatai.regist(p2);
p1.send('我目前高度500米',p2);

原型模式

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
`use strict`
//深拷贝
var obj = {
name:123,
address: ["shanghai","beijing",{road:"huangpu",num:100,room:108}]
};
function deepClone(obj){
var ret = {},b;
if( (b=(obj instanceof Array)) || (obj instanceof Object) ){
if(b){
ret = [];
}
for(k in obj){
if( (obj[k] instanceof Array) || (obj[k] instanceof Object) ){
ret[k] = deepClone(obj[k]);
}else{
ret[k] = obj[k];
}
}
}
return ret;
}
var obj1 = deepClone(obj);
obj1.address[2].road = "pujiang";
console.info(obj);
console.info(obj1);

使用Object.create() 来实现面向对象继承,避免执行两次构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
`use strict`
function Father(name,age){
this.fname = name;
this.fage = age;
}
Father.prototype.hello = function() {
console.info("hello, i am " + this.fname);
};
function Son(){
Father.call(this,[].shift.call(arguments));
}
Son.prototype = Object.create(Father.prototype);
Son.prototype.pntAge = function() {
console.info(`I am ${this.fage} years old.`);
};
var s1 = new Son('s1',15);
console.info(s1 instanceof Father);
s1.hello();
s1.pntAge();

模板模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
`use strict`
function shangdi(){}
shangdi.prototype.zaoren_yanjin = function(){
console.info('眼睛');
};
shangdi.prototype.zaoren_bizi = function(){
console.info('鼻子');
};
shangdi.prototype.aihao = function(){
throw new Error('我只是个钩子,需要你自己去探索!');
}
/*小明*/
function xiaoming(){
console.log('小明是上帝的子类');
shangdi.call(this);
}
xiaoming.prototype = new shangdi;
xiaoming.prototype.aihao = function() {
console.info('小明爱好讲笑笑话!');
};
var xm = new xiaoming();
xm.aihao();

装饰模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
`use strict`
function House(){};
House.prototype.info = function(){
console.info("一个空的房间!");
};
function Decorate(house){
this.house = house;
};
Decorate.prototype.info = function(){
this.house.info();
console.info('装饰器,添加了一张桌子');
}
var house = new Decorate(new House());
house.info();

组合模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
`use strict`
function Dinner(){};
Dinner.prototype.coffe = function() {
throw new Error("不能直接使用");
};
Dinner.prototype.bread = function() {
throw new Error("不能直接使用");
};
function Customer(dinner){
this.dinner = dinner;
}
Customer.prototype.order = function(){
this.dinner.coffe = function(){
console.info('顾客的咖啡');
}
this.dinner.bread = function(){
console.info('顾客的面包');
}
this.dinner.coffe();
this.dinner.bread();
}
var c = new Customer(new Dinner());
c.order();