子慕談設計模式系列(三)


前言

設計模式不容易用文字描述清楚,而過多的代碼,看起來也讓人摸不到頭腦,加上詞語或者文字描述的抽象感,很容易讓人看了無數設計模式的文章,也仍然理解不了。  所以我一直打算寫此系列博客,首先我會從大量文章里去理解這些設計模式,最后我用自己的語言組織轉化為博客,希望用更少的代碼,更容易理解的文字,來聊一聊這些設計模式。  我所理解、所描述的每一個設計模式也可能有些是錯誤的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客內容。  我作為前端開發者,所以設計模式的代碼以前端代碼和視角為主。  此博客內容對每一種模式並不會寫得非常深入,也許能為讀者打通一些認知,如果看了此系列博客,再去看其他更深入的博客,可能是一種比較好的方式。

 

代理模式

代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。

生活中的例子:買火車票不一定在火車站買,也可以去代售點或者網上購買,vpn。

常見的代理模式應用例子:前后端交互,往往后端有server層和應用層,前端只和后端應用層打交道,應用層調取server層,server層職責單一,應用層處理邏輯。  應用層既是代理層,也可以叫中間件。

代理模式最開始我一直沒有想到平時前端應用中的例子。但是經過多次推敲和聯系,發現代理模式的使用無處不在,再讀一遍描述(在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。)。那么我認為對API的封裝可以算是代理模式的應用,一般對API進行封裝,肯定是希望在調用API的時候做一些通用的事情或者處理,以便讓對象的調用更方便或者更適應業務。比如我們要封裝一下js的location.href的跳轉。我們想在跳轉之前進行一些統計,那么我們可以在location.href前處理跳轉邏輯。但是因為要跳轉的地方太多了,我不想每個地方都有跳轉邏輯,所以我封裝一個jump方法,專門處理跳轉,並在跳轉之前處理統計邏輯。而后我在跳轉前還需要做其他事情,就可以直接在這個jump方法里實現,擴展性也非常高。

function jump(url){
	//統計邏輯
	location.href = url;
}
jump('baidu.com')

優點:

  • 職責清晰
  • 代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了中介的作用和保護了目標對象的作用。
  • 高擴展性

缺點:

  • 會使程序處理和響應速度變慢

 

中介者模式

中介者模式是用來降低多個對象和類之間的交互復雜性。這種模式提供了一個中介類,該類通常處理不同類之間的交互,並支持松耦合,使代碼易於維護。  中介者模式屬於行為型模式。

用一個中介對象來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。  對象與對象之間存在大量的關聯關系,這樣勢必會導致系統的結構變得很復雜,形成網狀結構。

典型應用例子:MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。

相信現在的框架模式至少都是mvc或者mvvm。我曾經看過一個運行的老項目.net代碼,一個aspx文件中寫了html,css,js,.net,並且數據庫查詢操作也寫到頁面中了,那頁面維護起來就非常酸爽了。  如果使用mvc模式,控制器作為中介,隔離model和view讓它們不直接交互,達到了解耦效果,雖然會增加代碼量,但是交互的可重用性得到了提升,代碼復雜度降低,邏輯更加清晰,更易於維護,也就是所謂高內聚低耦合。

現實中的例子:在學校下發一個通知的時候,如果是通過口口相傳的方式,那么學生之間的關系是多對多的關系,非常難以准確傳達到每個人手里。如果學校貼一張公告單到校門口,那么公告單作為學校和學生的中介,就能讓關系變得簡單。我們平時生活中的聊天群也可以達到同樣效果。網狀結構的關系由此變成了星型結構。如下盜圖:

 

 

觀察者模式

觀察者模式(有時又被稱為發布-訂閱模式)是設計模式的一種。  在此種模式中, 一個或者多個觀察者去監聽主題,當主題發生變化的時候,主題會通知所有的觀察者。  這通常透過調用觀察者所提供的方法來實現。此種模式通常被用來實現事件處理系統。  比如常用的事件監聽,onclick,onload等。 

vue采用數據劫持結合觀察者模式,通過Object.defineProperty方法來劫持各個屬性的settergetter,在數據變動時發布消息給訂閱者,觸發相應的回調。  寫一個刪減版的類似例子(直接復制代碼在控制台運行試試):

function Observer(data){//傳入觀察對象
    this.data = data;
    this.defineReactive(data);
}

Observer.prototype = {
    defineReactive: function(data){
        var self = this;
        var dep = new Dep();
        dep.addSub(data);//添加訂閱

        Object.keys(data).forEach(function(key){
            var val = data[key];
            Object.defineProperty(data, key, {
                get: function(){
                    return val;
                },
                set: function(newVal){
                    if(val == newVal) return;
                    val = newVal;

                    dep.notify();//發生改變下發通知
                }
            })
        })
    }
}

function Dep(){}
Dep.prototype = {
    addSub: function(sub){
        this.sub = sub;
    },
    notify: function(){
        console.log(this.sub);//當前對象有值的改變,打印出當前對象
    }
}

var data = {
    val: 1
}
new Observer(data);
data.val = 2;//執行后,在控制台會打印出當前對象,直接復制這段代碼運行試試

 

策略模式

  • 定義了一系列算法;
  • 封裝了每個算法;
  • 這一系列的算法可互換代替。
策略模式是指對一系列的算法定義,並將每一個算法封裝起來,而且使它們還可以相互替換,讓算法僅僅只做算法,讓其它邏輯的事情交給策略類去處理。 下面直接用例子說明。
我們常會封裝一個驗證工具類,一般的寫法是定義一個對象字面量,不同的屬性定義不同的驗證方法,大致如下代碼:
var validator = {
    isNumber:function(val){
        if(false) alert('非法數字');
        //return true or false
    },
    isPhone:function(){}
    //.....  
}

 這樣在使用和維護驗證的時候,重復代碼量會比較多,一個驗證方法需要處理驗證算法和驗證后的邏輯(驗證返回、提醒等),不管提醒是寫在驗證方法內還是在外部方法外,這些邏輯每次都要處理一次。  那么使用策略類,來統一處理驗證邏輯,並把驗證算法獨立開,這樣之后維護只修改和添加算法方法。  那么怎么做呢,如下代碼:

var validator = {
    validate: function(type, val){
        for (prop in this.types) {
            if(type == prop){//調用對應驗證方法
                var result = this.types[type].validate(val);
                if(!result) alert(this.types[type].text);
                return result;
            }
        }
    },

    types: {//定義驗證算法
        isNumber:{
            validate: function(val){
                //判斷
                return true or false;
            },
            text: '非法數字'
        },
        isPhone:function(){}
        //.....  
    }
}

var input1 = $('input').val();
validator.validate('isNumber', input1)

以上代碼只是一個小demo,並不完善,想表達的意思就是策略模式,把算法和策略類獨立開來,根據需求算法可以替換,策略類統一處理。  angular的驗證類FormGroup就使用了這一模式。  上面的demo因為還比較簡單,所以一下感覺不到有多大差別,但是如果你真正使用過類型angular的FormGroup這類的驗證方式,你就會發現能節省很多代碼量,邏輯也非常清晰。

 

狀態模式

允許一個對象在其內部狀態改變時改變它的行為。  對象看起來似乎修改了它的類。通俗的說,對象內部定義了不同狀態的類,在狀態改變的時候,替換對象相應的類。

舉一個生活中的例子:當我們的手機電量比較充足的時候,手機會使用正常模式用電,當電量小於20%的時候,手機會使用省電模式。電量就是狀態,正常模式和省電模式就是相應的替換類。

再舉一個前端的例子: 博客園的后台首頁右上角,有兩種形態,一種是登錄之后,會顯示當前用戶名和注銷按鈕,一種是沒有登錄會展示登錄按鈕和注冊按鈕。  假如博客園使用了雙向綁定, 那么在模板里,會有登錄和未登錄的html代碼,然后通過一個指令(比如ngShow)傳入控制器變量操作誰隱藏誰顯示。  那么控制器的變量就是狀態,登錄和未登錄模板代碼就是狀態對應的類。  這個模式我想作為前端肯定是會常常使用的。所以此模式就不用代碼描述了。

 

此系列博客目錄:

 

子慕談設計模式系列(一)

子慕談設計模式系列(二)——設計模式六大原則

子慕談設計模式系列(三)


免責聲明!

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



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