js設計模式


目錄

前言

本文參考於《javascript模式》,因此會大量內容會和書中相同,手上有這本書的朋友可以直接看書。因為我的記憶習慣是抄書,所以我會先抄寫下來再發到博客上。
更新中。

單體模式

單體模式思想在於保證一個特定類僅有一個實例,意味着當你第二次使用同一個類創建信對象時,應得到和第一次創建對象完全相同。

方法一

1
2
3
4
5
6
7
8
9
10
11
12
function Universe(){
if(typeof Universe.instance==="object"){
return Universe.instance; //防止被篡改
}
this.xx="xx";
Universe.instance= this;
return this;
}
 
var uni=new Universe();
var uni2=new Universe();
uni===uni2; //true

缺點

instance 屬性暴露。

方法二

使用閉包

1
2
3
4
5
6
7
8
9
10
11
function Universe(){
var instance=this; //緩存this
this.xx="xx";
Universe= function(){ //重寫此構造函數
return instance;
}
}
 
var uni=new Universe();
var uni2=new Universe();
uni===uni2; //true

缺點

因為重寫了構造函數,constructor 還是指向了老的構造函數,且實例化后在添加原型屬性也是不一樣的。如下

1
2
3
4
5
6
7
var uni = new Universe();
Universe.prototype.a = 1
var uni2 = new Universe();
console.log(uni === uni2) //true
console.log(uni.a) //undefinded
console.log(uni2.a) //undefinded
console.log(uni.constructor === Universe); //false

方法三

解決方法二問題。

1
2
3
4
5
6
7
8
9
10
function Universe(){
var instance;
Universe= function Universe(){
return instance ;
}
Universe.prototype= this; //保存原型屬性
instance= new Universe();
instance.constructor=Universe;
instance.xx= "xx";
}

方法四

自運行函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Universe;
( function(){
var instance;
Universe= function Universe(){
if(instance){
return instance;
}
instance= this;
this.xx="xx";
}
})();
 
var uni = new Universe();
Universe.prototype.a = 1
var uni2 = new Universe();
console.log(uni === uni2) //true
console.log(uni.a) //1
console.log(uni2.a) //1
console.log(uni.constructor === Universe); //true

工廠模式

工廠模式是為了創建對象。

例子

  • 公共構造函數 CarMaker
  • 名為factory的CarMaker靜態方法來創建car對象
1
2
3
4
5
6
var corolla=CarMaker.factory('compact');
var solstice=CarMaker.factory('convertible');
var cherokee=CarMaker.factory('suv');
corolla.drive() //I have 4 doors
solstice.drive() //I have 2 doors
cherokee.drive() //I have 6 doors

實現

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 CarMaker() {}
CarMaker.prototype.drive = function() {
return "I have " + this.doors + " doors";
}
CarMaker.compact = function() {
this.doors = 4;
}
CarMaker.convertible = function() {
this.doors = 2
}
CarMaker.suv = function() {
this.doors = 6;
}
CarMaker.factory = function(type) {
if (typeof CarMaker[type] !== "function") {
throw "Error"
}
if (typeof CarMaker[type].prototype.drive !== "function") {
CarMaker[type].prototype = new CarMaker();
}
var newCar = new CarMaker[type]();
return newCar;
}
var corolla = CarMaker.factory('compact');
console.log(corolla.drive()); //I have 4 doors

內置工廠對象

Object() 構造函數即為內置工廠對象。

迭代器模式

有一個包含某種數據集合的對象,該數據可能存儲在一個復雜數據結構內部,而要提供一個簡單方法訥訥感訪問到數據結構中沒一個元素。

  • next() 下一個
  • hasNext() 是否有下一個
  • reWind() 重置指針
  • current() 返回當前
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
var agg = (function() {
var index = 0;
var data = [1, 2, 3, 4, 5, 6];
var length = data.length;
return {
next: function() { //這里是從第一個數據開始輸出 本例中為 1
if (!this.hasNext()) {
return null;
}
var element = data[index];
index++;
return element;
},
hasNext: function() {
return index < length;
},
reWind: function() {
index = 0;
},
current: function() {
return data[index];
}
}
})();
 
while (agg.hasNext()) {
console.log(agg.next()); //1,2,3,4,5,6
}
agg.reWind(); //此時重置指針到0

裝飾者模式

可以在運行時候添加附加功能到對象中,他的一個方便特征在於其預期行為的可定制和可配置特性。

例子 假設在開發一個銷售商品的Web應用,每一筆信銷售都是一個人新的 sale 對象。該對象“知道”有關項目的價格,並可以通過 getPrice() 方法返回加個。
根據不同情況,可以用額外的功能裝飾此對象。
假設客戶在魁北克省,買房需要支付聯邦稅和魁北克省稅,則此時需要調用聯邦稅裝飾者和魁北克省稅裝飾者。

1
2
3
4
5
var sale=new Sale(100);
sale=sale.decorate( "fedtax"); //聯邦稅
sale=sale.decorate( "quebec"); //魁北克省稅
sale=sale.decorate( "miney"); //轉為美元格式
sale.getPrice(); //返回價格

並且裝飾是可選的,例如不再魁北克省有可能沒有省稅。

方法一

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
54
55
56
57
58
59
function Sale(price) {
this.price = price;
}
 
Sale.prototype.getPrice = function() {
return this.price;
};
 
Sale.decorators = {}; //儲存裝飾者的對象
 
//裝飾者
 
Sale.decorators.fedtax = {
getPrice: function() {
var price = this.uber.getPrice();
return price * 0.8; //對price進行處理
},
}
 
Sale.decorators.quebec = {
getPrice: function() {
var price = this.uber.getPrice();
return price * 0.7; //對price進行處理
},
}
 
Sale.decorators.money = {
getPrice: function() {
var price = this.uber.getPrice();
return "$" + price * 0.9; //對price進行處理
},
}
 
/*decorate() 方法
調用裝飾者方法 sale.=sale.decorate("fedtax");
fedtax字符串對應 Sale.decorators中的對象屬性。新裝飾對象 newobj 將繼承目前我們所擁有的對象,這就是ixiangthis
為了完成繼承部分代碼,此時需要一個臨時構造函數,先設置 newobj 的 uber 屬性,以便於自對象可以訪問到父對象。之后從裝飾者中
將所有的額外屬性復制到新裝飾的對象 newobj 中,最后返回 newobj。
*/
 
Sale.prototype.decorate = function(decorate) {
var F = function() {};
var overrides = this.constructor.decorators[decorate]; //獲取裝飾者對象
F.prototype = this;
var newobj = new F();
newobj.uber = F.prototype;
for (var key in overrides) {
if (overrides.hasOwnProperty) { //判斷對象是不是自身的
newobj[key] = overrides[key];
}
}
return newobj;
};
 
var sale = new Sale(100);
sale = sale.decorate( "fedtax"); //聯邦稅
sale = sale.decorate( "quebec"); //魁北克省稅
sale = sale.decorate( "money"); //轉為美元格式
console.log(sale.getPrice()); //$50.4

方法二

此方法使用列表實現,而且相對來說比較好理解一點。本質就是把裝飾者名稱保存到一個列表中並且一次調用此列表中的方法。

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
function Sale(price) {
this.price = price;
this.decorateList = [];
}
Sale.decorators = {};
 
Sale.decorators.fedtax = {
getPrice: function(price) {
var price = this.uber.getPrice();
return price * 0.8; //對price進行處理
},
}
 
Sale.decorators.quebec = {
getPrice: function(price) {
var price = this.uber.getPrice();
return price * 0.7; //對price進行處理
},
}
 
Sale.decorators.money = {
getPrice: function(price) {
var price = this.uber.getPrice();
return "$" + price * 0.9; //對price進行處理
},
}
 
Sale.prototype.decorate = function(decorator) {
this.decorateList.push(decorator);
};
 
Sale.prototype.getPrice = function() {
var price = this.price;
this.decorateList.forEach(function(name) {
price = Sale.decorators[name].getPrice(price);
});
return price;
};
 
var sale = new Sale(100);
sale = sale.decorate( "fedtax"); //聯邦稅
sale = sale.decorate( "quebec"); //魁北克省稅
sale = sale.decorate( "money"); //轉為美元格式
console.log(sale.getPrice()); //$50.4

策略模式

策略模式支持在運行時候選擇算法。例如用在表單驗證問題上,可以創建一個具有 validate() 方法的驗證器對象,無論表單具體類型是什么,該方法都會被調用,
並且返回結果或者錯誤信息。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
var validator = {
 
// 所有可以的驗證規則處理類存放的地方,后面會單獨定義
types: {},
 
// 驗證類型所對應的錯誤消息
messages: [],
 
// 當然需要使用的驗證類型
config: {},
 
// 暴露的公開驗證方法
// 傳入的參數是 key => value對
validate: function (data) {
 
var i, msg, type, checker, result_ok;
 
// 清空所有的錯誤信息
this.messages = [];
 
for (i in data) {
if (data.hasOwnProperty(i)) {
 
type = this.config[i]; // 根據key查詢是否有存在的驗證規則
checker = this.types[type]; // 獲取驗證規則的驗證類
 
if (!type) {
continue; // 如果驗證規則不存在,則不處理
}
if (!checker) { // 如果驗證規則類不存在,拋出異常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
 
result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},
 
// helper
hasErrors: function () {
return this.messages.length !== 0;
}
};
 
//然后剩下的工作,就是定義types里存放的各種驗證類了
 
// 驗證給定的值是否不為空
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "傳入的值不能為空"
};
 
// 驗證給定的值是否是數字
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "傳入的值只能是合法的數字,例如:1, 3.14 or 2010"
};
 
// 驗證給定的值是否只是字母或數字
validator.types.isAlphaNum = {
validate: function (value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: "傳入的值只能保護字母和數字,不能包含特殊字符"
};
//使用的時候,我們首先要定義需要驗證的數據集合,然后還需要定義每種數據需要驗證的規則類型,代碼如下:
var data = {
first_name: "Tom",
last_name: "Xu",
age: "unknown",
username: "TomXu"
};
 
validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};
 
//最后獲取驗證結果
 
validator.validate(data);
 
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}

策略模式定義及例子實現參考與《javascript模式》及 湯姆大叔的博客

外觀模式

外觀模式即讓多個方法一起被調用

例如。 stopPropagation() 和 preventDefault() 兼容性一起調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myEvent = {
stop: function(e) {
if (typeof e.preventDefault() === "function") {
e.preventDefault();
}
if (typeof e.stopPropagation() === "function") {
e.stopPropagation();
}
//for IE
if (typeof e.returnValue === "boolean") {
e.returnValue = false;
}
if (typeof e.cancelBubble === "boolean") {
e.cancelBubble = true;
}
},
}

代理模式

在代理模式中,一個對象充當另外一個對象的接口,和外觀模式區別是:外觀模式是合並調用多個方法。
代理模式是介於對象的客戶端和對象本身之間,並且對該對象的訪問進行保護。

包裹例子

現在有個包裹,賣家要把這個包裹寄給gary,則需要通過快遞公司寄過來,此時快遞公司就是一個 proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var package = function(receiver) {
this.receiver = receiver;
}
var seller = function(package) {
this.package = package;
this.send = function(gift) {
return package.receiver + "你的包裹:" + gift;
}
}
var express = function(package) {
this.package = package;
this.send = function(packageName) {
return new seller(package).send(packageName);
}
}
 
//調用
 
var ems = new express(new package("gary"));
console.log(ems.send("鍵盤")); //gary你的包裹:鍵盤

論壇權限管理例子

本例子參考與 大熊君

  • 權限列表
    • 發帖 1
    • 帖子審核 2
    • 刪帖 3
    • 留言、回復 4
用戶 代碼 權限
注冊用戶 001 1 4
論壇管理員 002 2 3 4
系統管理員 003 1 2 3 4
游客 000 null

用戶類

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
function User(name, code) {
this.name = name;
this.code = code;
}
User.prototype.getName = function() {
return this.name;
};
 
User.prototype.getCode = function() {
return this.code;
};
 
User.prototype.post = function() {
//發帖功能
};
 
User.prototype.remove = function() {
// 刪帖功能
};
 
User.prototype.check = function() {
//審核
};
 
User.prototype.comment = function() {
//留言回復
};

論壇類

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
 
function Forum(user) {
this.user=user;
}
 
Forum.prototype.getUser = function () {
return this.user;
};
 
Forum.prototype.post = function () {
var code=this.user.getCode();
if(code=="001"||code=="003"){
return this.user.post();
} else{
return false;
}
};
 
Forum.prototype.remove = function () {
var code=this.user.getCode();
if(code=="002"||code=="003"){
return this.user.remove();
} else{
return false;
}
};
 
Forum.prototype.check = function () {
var code=this.user.getCode();
if(code=="002"||code=="003"){
return this.user.check();
} else{
return false;
}
};
 
Forum.prototype.comment = function () {
var code=this.user.getCode();
if(code=="001"||code=="002"||code=="003"){
return this.user.comment();
} else{
return false;
}
};

運行

1
new Forum(new User("administartor","003"));

中介者模式

中介者模式可以讓多個對象之間松耦合,並降低維護成本

例如:游戲程序,兩名玩家分別給與半分鍾時間來競爭決出勝負(誰按鍵的次數多勝出,這里玩家1按1,玩家2按0)

  • 計分板(scoreboard)
  • 中介者 (mediator)

中介者知道所有其他對象的信息。他與輸入設備(此時是鍵盤)進行通信並處理鍵盤上的按鍵時間,之后還將消息通知玩家。玩家玩游戲同時(每一分都更新自己分數)還要
通知中介者他所做的事情。中介者將更新后的分數傳達給計分板。

除了中介者莫有對象知道其他對象。

圖示

圖示

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
54
55
56
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function() {
this.points += 1;
mediator.played();
};
var scoreboard = {
element: "這里是獲取的element用於展示分數",
update: function(score) { //更新分數
var msg;
for (var key in score) {
if (score.hasOwnProperty(key)) {
msg += score[key];
}
}
this.element.innerText = msg;
},
}
 
var mediator = {
players: {}, //玩家對象
setup: function() {
var players = this.players;
players.home = new Player("home");
players.guest = new Player('guest');
},
played: function() {
var players = this.players;
var score = {
home: players.home.points,
guest: players.guest.points
}
},
keypress: function(e) {
e = e || window.event;
if (e.which === 49) { //or keycode 對應按鍵 1
mediator.players.home.play();
return;
}
if (e.which === 48) { // 對應按鍵 0
mediator.player.guest.play();
return;
}
},
}
 
//運行
 
mediator.setup();
window.onkeypress = mediator.keypress;
setTimeout( function() { //設置30秒游戲時間
window.onkeypress = null;
alert( "game end");
}, 30000);

觀察者模式

觀察者模式在 javascript 中使用非常廣泛。所有的瀏覽器時間就是該模式的實現,node.js中的events也是此模式實現。
此模式另一個名稱是 訂閱/發布模式 。
設計這種模式原因是促進形成松散耦合,在這種模式中,並不是一個對象調用另一個對象的方法,而是一個對象訂閱另一個對象的
特定活動並在狀態改編后獲得通知。訂閱者因此也成為觀察者,而被觀察的對象成為發布者或者主題。當發生了一個重要事件時候
發布者會通知(調用)所有訂閱者並且可能經常已事件對象的形式傳遞消息。

例子

我之前有篇文章即是觀察者模式例子

nodejs的EventEmitter

小結

1.單體模式

針對一個類僅創建一個對象。

2.工廠模式

根據字符串制定類型在運行時創建對象的方法。

3.迭代器模式

提供一個API來遍歷或者操作復雜的自定義數據結構。

4.裝飾者模式

通過從預定義裝飾者對象中添加功能,從而在運行時侯調整對象

5.策略模式

在懸在最佳策略以處理特定任務的時候仍然保持相同的接口。

6.外觀模式

通過把常用方法包裝到一個新方法中,從來提供一個更為便利的API。

7.代理模式

通過包裝一個對象從而控制對它的訪問,其中主要方法是將方位聚集為租或者
僅當真正必要時侯才執行訪問,從未避免高昂的操作開銷。

8.終結者模式

通過是你的對象之間相互不直接“通話”,而是通過一個中介者對子昂進行通信,
從而形成松散耦合。

9.觀察者模式

通過創建“可觀察”的對象,當發生一個感興趣的事件時可將改時間通告給所有觀察者
從而形成松散耦合。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM