設計模式簡單說明
概念:設計模式是對軟件設計中普遍存在(反復出現)的各種問題所提出的解決方案。
優點:為了可重用代碼,讓代碼更容易被他人理解,保證代碼可靠性。
分類:
創建型模式:工廠方法模式+ 抽象工廠模式 +單粒模式 + 建造者模式 + 原型模式;
結構型模式:適配器模式 + 代理模式 + ···;
行為型模式:觀察者模式 + 命令模式 + ···
工廠模式
簡單說明:目的是用于創建對象,通常在類或者是類的靜態方法中實現。
目標:
- 用一套方法去創建相似的目標;
- 在編譯時不知道具體類型的情況下,為用戶提供創建對象的接口;
核心步驟:
- 提供一個父構造函數;
- 在父構造函數的原型上添加共享的方法;
- 在父構造函數身上提供一個靜態方法(靜態工廠方法);
- 先獲取參數(產品類型);
- 判斷構造函數是否存在(容錯性處理);
- 設置原型鏈繼承:設置子構造函數的原型對象為父構造函數的一個實例對象;
- 使用子構造函數創建實例對象;
- 返回新創建的實例對象.
- 定義特定的工廠客戶(靜態方法);
- 通過父構造函數的靜態方法來創建產品對象。
//1. 提供一個父構造函數;
function PhoneMake() {
}
//2. 在父構造函數的原型上添加共享的方法;
PhoneMake.prototype.logDes = function () {
console.log('我們的口號是' + this.des);
}
//3. 在父構造函數身上提供一個靜態方法(靜態工廠方法);
PhoneMake.factory =function (typeStr) {
//1.先獲取參數(產品類型)
var type = typeStr;
//2.判斷構造函數是否存在(容錯性處理)
if(typeof PhoneMake[type] != 'function'){
throw '暫時無法生產此款手機!'
}
//3.設置原型鏈繼承:設置子構造函數的原型對象為父構造函數的一個實例對象
PhoneMake[type].prototype = new PhoneMake();
//4.使用子構造函數創建實例對象
var obj = new PhoneMake[type]();
//5.返回新創建的實例對象
return obj;
}
//4. 定義特定的工廠客戶(靜態方法);
PhoneMake.iphone = function () {
this.des = '最安全,最好用的手機'
}
PhoneMake.oppo = function () {
this.des = '照亮你的美!'
}
PhoneMake.vivo =function () {
this.des = '充電五分鐘,通話兩小時'
}
PhoneMake.meizu = function () {
this.des = '我就是我,不一樣的煙火'
}
//5. 通過父構造函數的靜態方法來創建產品對象。
var iphone = PhoneMake.factory('iphone');
var oppo = PhoneMake.factory('oppo');
var vivo = PhoneMake.factory('vivo');
var meizu = PhoneMake.factory('meizu');
iphone.logDes();
oppo.logDes();
vivo.logDes();
meizu.logDes();
huawei.logDes();
單例模式
思想:保證一個特定的類只有一個實例,即當我們第二次創建新對象的時候,得到的應該是和第一次創建的對象一模一樣的對象(同一個對象)。
JavaScript中的單例模式
JavaScript是一門弱類型,動態,基于原型的語言,并沒有類,只有對象。
在JavaScript中要實現單例模式有很多種方式。
- 使用全局變量方式存儲創建出來的實例對象
思想:- 提供一個全局變量;2. 提供一個構造函數;3. 判斷這個全局變量是否有值,如果有值直接返回;4. 如果沒有值,就把this賦值給全局變量;5. 通過this設置屬性和方法。
問題:使用一個全局變量來實現單例,這個全局變量在整個作用域中都可以被訪問或者修改,很容易導致被覆蓋或者修改。修改后創建出來的對象就不再是之前的單例對象了。
- 提供一個全局變量;2. 提供一個構造函數;3. 判斷這個全局變量是否有值,如果有值直接返回;4. 如果沒有值,就把this賦值給全局變量;5. 通過this設置屬性和方法。
var instance ;
function Person() {
if(instance){
console.log('再一次創建對象,直接返回之前的對象');
return instance;
}
instance = this;
this.name = 'zs';
this.age = 20;
console.log('第一次創建對象');
}
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);//true
instance = 'demo';//修改了全局變量instance的值,
var p3 = new Person();
console.log(p1 == p3);//false
全局變量方式實現單例-即時函數
說明:通過即時函數來限定作用域,外部無法修改內部instance的值(現在是一個局部變量)。
var Person;
(function () {
var instance;
Person = function () {
if(instance){
return instance;
}
instance = this;
this.name = '默認';
}
})();
var p1 = new Person();
var p2 = new Person();
- 通過構造函數靜態屬性來緩存實例對象
思想:- 提供一個構造函數;2. 在內部判斷構造函數的靜態屬性中是否擁有實例對象;3.把內部創建的實例化對象賦值給構造函數的靜態屬性;4. 創建實例對象;5. 通過this設置屬性和方法。
問題:在構造函數外部可以直接訪問其靜態成員(屬性和方法),可能會導致實例對象的丟失。
//1. 提供一個構造函數;
function Person() {
// 2. 在內部判斷構造函數的靜態屬性中是否擁有實例對象;
if(Person.instance){
console.log('之前創建過,直接返回');
return Person.instance;
}
//設置實例對象的屬性和方法
this.name = 'zs';
console.log('第一次創建');
//3.把內部創建的實例化對象賦值給構造函數的靜態屬性;
Person.instance = this;
}
// 4. 創建實例對象;
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);//true
// 5. 通過this設置屬性和方法。
Person.instance = 'demo'
var p3 = new Person();
console.log(p1 == p3);//false
- 通過惰性函數定義來實現
思路:
1.提供一個構造函數;2.在構造函數內部提供一個私有變量來緩存實例;3. 利用惰性函數定義更新構造函數;4.把this賦值instance;5.通過this設置屬性和方法
注意點: 1.創建出來的單粒對象的構造器屬性始終指向舊的構造函數;2.創建單粒對象之后設置的原型對象和這個單粒對象的原型對象不是同一個,設置到原型上的屬性和方法無法訪問。
//1.提供一個構造函數
function Person() {
//2.在構造函數內部提供一個私有變量
var instance;
//3. 利用惰性函數定義更新構造函數
Person = function () {
return instance;
}
//4.把this賦值instance
instance = this ;
//5.通過this設置屬性和方法
this.name = '默認的';
this.age = 20;
}
Person.prototype.des = 'des';
var p1 = new Person();
Person.prototype.log = 'log';
var p2 = new Person();
console.log(p1.constructor == Person);//false
console.log(p1.des); //des
console.log(p2.des); //des
console.log(p1.log); // undefined
console.log(p2.log); // undefined
解決以上惰性函數問題思路:
1.提供一個構造函數;2.在構造函數內部提供一個私有變量;3.利用惰性函數定義更新構造函數;4.設置原型鏈繼承;5.調用new構造函數方法創建一個實例化對象賦值給instance;6.修正instance實例的構造器屬性,指向新的構造函數;7. 設置實例屬性和方法;8. 返回instance對象。
//1.提供一個構造函數
function Person() {
//2.在構造函數內部提供一個私有變量
var instance;
//3. 利用惰性函數定義更新構造函數
Person = function () {
return instance;
}
// 4.設置原型對象(設置新構造函數的原型是舊構造函數的原型)
Person.prototype = this;
// 5.利用新構造函數創建對象
instance = new Person();
//6.修正構造器屬性
instance.constructor = Person;
//7.通過instance設置屬性和方法
instance.name = '默認的';
//8.返回instance 對象
return instance ;
}
Person.prototype.des = 'des';
var p1 = new Person();
Person.prototype.log = 'log';
var p2 = new Person();
console.log(p1.constructor == Person);//true
console.log(p1.des); //des
console.log(p2.des); //des
console.log(p1.log); // log
console.log(p2.log); // log
觀察者模式
觀察者模式又名為發布-訂閱者模式,它定義了對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴它的對象都將得到通知。
優點
- 觀察者模式可以廣泛應用于異步編程中,這是一種替代傳遞回調函數的方案;
- 觀察者模式可以取代對象之間硬性編碼的通知機制,一個對象不再是顯示的調用另外一個對象的接口,這種模式讓兩個對象松耦合的聯系在一起,它們不需要清楚彼此的實現細節就能夠相互通信;
- 在這種設計模式中,不再是一個對象調用另外一個對象的方法,而是一個對象訂閱另一個對象的特定活動,并且在狀態改變后獲得通知。
模式
- 訂閱者也稱為觀察者;
- 被觀察的對象稱為發布者或者是主題;
- 當發生一個重要事件的時候,發布者將會通知所有訂閱者并且經常以事件的形式來傳遞消息。
思想
- 提取成公共的發行者對象;
- 提供一個工具函數,能夠利用發行者對象的模板來快速創建新的發布者;
- 創建發布者;
- 創建訂閱者(當發布者發布消息的時候,訂閱者能夠收到信息--自動調用訂閱者的方法)
//1. 提取公共的發行者對象;
var publisher = {
addUser: function (fn, type) {
//對訂閱的類型進行判斷
var type = type || 'eat';
if (typeof this.user[type] == 'undefined') {
this.user[type] = [];
}
if (typeof fn != 'function') {
throw '不支持'
}
this.user[type].push(fn);
},
removeUser: function (fn, type) {
this.publish(type,fn);
},
publish: function (type, fn) {
var type = type || 'eat';
for (var i = 0; i < this.user[type].length; i++) {
//判斷是發布狀態還是移除狀態
if (typeof fn == 'function') {
//移除狀態
if (this.user[type][i] == fn) {
this.user[type].splice(i, 1);
}
}
else {
//發布狀態
this.user[type][i]();
}
}
}
};
var rose = {
eat : function () {
this.publish('eat');
},
sleep : function () {
this.publish('sleep')
},
//發布者變為訂閱者,處理關心狀態
rose_lol : function() {
console.log('一起開黑怎么樣?');
}
};
//2. 提供一個工具函數,能夠利用發行者對象的模板來快速創建新的發布者;
function makePublisher(obj) {
for (var key in publisher){
//只拷貝實例方法
if(publisher.hasOwnProperty(key) && typeof publisher[key] == 'function')
obj[key] = publisher[key];
}
obj.user = {
}
}
//3. 創建發布者;
makePublisher(rose);
//4. 創建訂閱者(當發布者發布消息的時候,訂閱者能夠收到信息--自動調用訂閱者的方法)
var jack = {
jack_eat :function () {
console.log('帶女神去吃麻辣燙--jack');
},
jack_sleep :function () {
console.log('給女神唱安眠曲--jack');
},
//訂閱者變發布者
lol :function () {
this.publish('lol')
}
};
var tom = {
tom_eat : function () {
console.log('帶女神吃火鍋');
},
tom_sleep : function () {
console.log('帶女神看星星');
},
tom_lol : function () {
console.log('開黑帶上我!');
}
};
makePublisher(jack);
// 注冊觀察者
jack.addUser(rose.rose_lol,'lol');
jack.addUser(tom.tom_lol,'lol');
rose.addUser(jack.jack_eat,'eat');
rose.addUser(tom.tom_sleep,'sleep');
//發布者發布信息
jack.lol();