JavaScript MVC模式

2018-08-02 16:26 更新

MVC

MVC是一個(gè)架構(gòu)設(shè)計(jì)模式,它通過(guò)分離關(guān)注點(diǎn)的方式來(lái)支持改進(jìn)應(yīng)用組織方式。它促成了業(yè)務(wù)數(shù)據(jù)(Models)從用戶界面(Views)中分離出來(lái),還有第三個(gè)組成部分(Controllers)負(fù)責(zé)管理傳統(tǒng)意義上的業(yè)務(wù)邏輯和用戶輸入。該模式最初是由Trygve Reenskaug在研發(fā)Smalltalk-80 (1979)期間設(shè)計(jì)的,當(dāng)時(shí)它起初被稱作Model-View-Controller-Editor。在1995年的“設(shè)計(jì)模式: 面向?qū)ο筌浖械目蓮?fù)用元素” (著名的"GoF"的書)中,MVC被進(jìn)一步深入的描述,該書對(duì)MVC的流行使用起到了關(guān)鍵作用。

Smalltalk-80 MVC

了解一下最初的MVC模式打算解決什么問(wèn)題是很重要的,因?yàn)樽詮恼Q生之日起它已經(jīng)發(fā)生了很大的改變?;氐?0年代,圖形用戶界面還很稀少,一個(gè)被稱為分離展示的概念開始被用來(lái)清晰的劃分下面兩種對(duì)象:領(lǐng)域?qū)ο?,它?duì)現(xiàn)實(shí)世界里的概念進(jìn)行建模(比如一張照片,一個(gè)人), 還有展示對(duì)象,它被渲染到用戶屏幕上進(jìn)行展示。

Smalltalk-80作為MVC的實(shí)現(xiàn),把這一概念進(jìn)一步發(fā)展,產(chǎn)生這樣一個(gè)觀點(diǎn),即把應(yīng)用邏輯從用戶界面中分離開來(lái)。這種想法使得應(yīng)用的各個(gè)部分之間得以解耦,也允許該應(yīng)用中的其它界面對(duì)模型進(jìn)行復(fù)用。關(guān)于Smalltalk-80的MVC架構(gòu),有幾點(diǎn)很有趣,值得注意一下:

  • 模型表現(xiàn)了領(lǐng)域特定的數(shù)據(jù),并且不用考慮用戶界面(視圖和控制器).當(dāng)一個(gè)模型有所改變的時(shí)候,它會(huì)通知它的觀察者。
  • 視圖表現(xiàn)了一個(gè)模型的當(dāng)前狀態(tài).觀察者模式被用來(lái)讓視圖在任何時(shí)候都知曉模型已經(jīng)被更新了或者被改變了。
  • 展現(xiàn)受到視圖的照管,但是不僅僅只有一個(gè)單獨(dú)的視圖或者控制器——每一個(gè)在屏幕上展現(xiàn)的部分或者元素都需要一個(gè)視圖-控制器對(duì)。
  • 控制器在這個(gè)視圖-控制器對(duì)中扮演著處理用戶交互的角色(比如按鍵或者點(diǎn)擊動(dòng)作),做出對(duì)視圖的選擇。

開發(fā)者有時(shí)候會(huì)驚奇于他們了解到的觀察者模式(如今已經(jīng)被普遍的作為發(fā)布/訂閱的變異實(shí)現(xiàn)了)已經(jīng)在幾十年以前被作為MVC架構(gòu)的一部分包含進(jìn)去了.在Smalltalk-80的 MVC中,視圖觀察著模型.如上面要點(diǎn)中所提到的,模型在任何時(shí)候發(fā)生了改變,視圖就會(huì)做出響應(yīng).一個(gè)簡(jiǎn)單的示例就是一個(gè)由股票市場(chǎng)數(shù)據(jù)支撐的應(yīng)用程序——為了應(yīng)用程序的實(shí)用性,任何對(duì)于我們模型中數(shù)據(jù)的改變都應(yīng)該導(dǎo)致視圖中的結(jié)果實(shí)時(shí)的刷新。

Martin Fowler在過(guò)去數(shù)年完成了對(duì)原生MVC有關(guān)問(wèn)題進(jìn)行寫作的優(yōu)秀工作,如果對(duì)關(guān)于Smalltalk-80的MVC的更深入的歷史信息感興趣的話,我建議您讀一讀他的作品。

JavaScript 開發(fā)者可以使用的 MVC

我們已經(jīng)回顧了70年代,讓我們回到當(dāng)下回到眼前?,F(xiàn)在,MVC模式已經(jīng)被應(yīng)用到大范圍的編程語(yǔ)言當(dāng)中,包括與我們關(guān)系最近的JavaScript。JavaScript領(lǐng)域現(xiàn)在有一些鼓勵(lì)支持MVC (或者是它的變種,我們稱之為MV* 家族)的框架,允許開發(fā)者不用付出太多的努力就可以往他們的應(yīng)用中添加新的結(jié)構(gòu)。

這些框架包括諸如Backbone, Ember.js和AngularJS??紤]到避免出現(xiàn)“意大利面條”式的代碼的重要性,該詞是指那些由于缺乏結(jié)構(gòu)設(shè)計(jì)而導(dǎo)致難于閱讀和維護(hù)的代碼,對(duì)現(xiàn)代JavaScript開發(fā)者來(lái)說(shuō),了解該模式能夠提供什么已經(jīng)是勢(shì)在必行。這使得我們可以有效的領(lǐng)會(huì)到,這些框架能讓我們以不同的方式做哪些事情。

我們知道MVC由三個(gè)核心部分組成:

Models

Models管理一個(gè)業(yè)務(wù)應(yīng)用的數(shù)據(jù)。它們既與用戶界面無(wú)關(guān)也與表現(xiàn)層無(wú)關(guān),相反的它們代表了一個(gè)業(yè)務(wù)應(yīng)用所需要的形式唯一的數(shù)據(jù)。當(dāng)一個(gè)model改變時(shí)(比如當(dāng)它被更新時(shí)),它通常會(huì)通知它的觀察者(比如我們很快會(huì)介紹的views)一個(gè)改變已經(jīng)發(fā)生了,以便觀察者采取相應(yīng)的反應(yīng)。

為了更深的理解models,讓我們假設(shè)我們有一個(gè)JavaScript的相冊(cè)應(yīng)用。在一個(gè)相冊(cè)中,照片這個(gè)概念配得上擁有一個(gè)自己的model, 因?yàn)樗砹颂囟I(lǐng)域數(shù)據(jù)的一個(gè)獨(dú)特類型。這樣一個(gè)model可以包含一些相關(guān)的屬性,比如標(biāo)題,圖片來(lái)源和額外的元數(shù)據(jù)。一張?zhí)囟ǖ恼掌梢源鎯?chǔ)到model的一個(gè)實(shí)例中,而且一個(gè)model也可以被復(fù)用。下面我們可以看到一個(gè)用Backbone實(shí)現(xiàn)的被簡(jiǎn)化的model例子。

var Photo = Backbone.Model.extend({

    // 照片的默認(rèn)屬性
    defaults: {
      src: "placeholder.jpg",
      caption: "A default image",
      viewed: false
    },

    // 確保每一個(gè)被創(chuàng)建的照片都有一個(gè)`src`.
    initialize: function() {
       this.set( { "src": this.defaults.src} );
    }

});

不同的框架其內(nèi)置的模型的能力有所不同,然而他們對(duì)于屬性驗(yàn)證的支持還是相當(dāng)普遍的,屬性展現(xiàn)了模型的特征,比如一個(gè)模型標(biāo)識(shí)符.當(dāng)在一個(gè)真實(shí)的世界使用模型的時(shí)候,我們一般也希望模型能夠持久.持久化允許我們用最近的狀態(tài)對(duì)模型進(jìn)行編輯和更新,這一狀態(tài)會(huì)存儲(chǔ)在內(nèi)存、用戶的本地?cái)?shù)據(jù)存儲(chǔ)區(qū)或者一個(gè)同步的數(shù)據(jù)庫(kù)中。

另外,模型可能也會(huì)被多個(gè)視圖觀察著。如果說(shuō),我們的照片模型包含了一些元數(shù)據(jù),比如它的位置(經(jīng)緯度),照片中所展現(xiàn)的好友(一個(gè)標(biāo)識(shí)符的列表)和一個(gè)標(biāo)簽的列表,開發(fā)者也許會(huì)選擇為這三個(gè)方面的每一個(gè)提供一個(gè)單獨(dú)的視圖。

為現(xiàn)代MVC/MV*框架提供一種將模型組合到一起的方法(例如,在Backbone中,這些分組作為“集合”被引用)并不常見。管理分組中的模型允許我們基于來(lái)自分組中所包含的模型發(fā)生改變的通知,來(lái)編寫應(yīng)用程序邏輯.這避免了手動(dòng)設(shè)置去觀察每一個(gè)單獨(dú)的模型實(shí)體的必要。

如下是一個(gè)將模型分組成一個(gè)簡(jiǎn)化的Backbone集合的示例:

var PhotoGallery = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Photo,

    // Filter down the list of all photos
    // that have been viewed
    viewed: function() {
        return this.filter(function( photo ){
           return photo.get( "viewed" );
        });
    },

    // Filter down the list to only photos that
    // have not yet been viewed
    unviewed: function() {
      return this.without.apply( this, this.viewed() );
    }
});

MVC上舊的文本可能也包含了模型管理著應(yīng)用程序狀態(tài)的一種概念的引述.Javascript中的應(yīng)用程序狀態(tài)有一種不同的意義,通常指的是當(dāng)前的"狀態(tài)",即在一個(gè)固定點(diǎn)上的用戶屏幕上的視圖或者子視圖(帶有特定的數(shù)據(jù)).狀態(tài)是一個(gè)經(jīng)常被談?wù)摰降脑掝},看一看單頁(yè)面應(yīng)用程序,其中的狀態(tài)的概念需要被模擬。

總而言之,模型主要關(guān)注的是業(yè)務(wù)數(shù)據(jù)。

視圖

視圖是模型的可視化表示,提供了一個(gè)當(dāng)前狀態(tài)的經(jīng)過(guò)過(guò)濾的視圖。Smaltalk的視圖是關(guān)于繪制和操作位圖的,而JavaScript的視圖是關(guān)于構(gòu)建和操作DOM元素的。

一個(gè)視圖通常是模型的觀察者,當(dāng)模型改變的時(shí)候,視圖得到通知,因此使得視圖可以更新自身。用設(shè)計(jì)模式的語(yǔ)言可以稱視圖為“啞巴”,因?yàn)樵趹?yīng)用程序中是它們關(guān)于模型和控制器的了解是受到限制的。

用戶可以和視圖進(jìn)行交互,包括讀和編輯模型的能力(例如,獲取或者設(shè)置模型的屬性值)。因?yàn)橐晥D是表示層,我們通常以用戶友好的方式提供編輯和更新的能力。例如,在之前我們討論的照片庫(kù)應(yīng)用中,模型編輯可以通過(guò)“編輯”視圖來(lái)進(jìn)行,這個(gè)視圖里面,用戶可以選擇一個(gè)特定的圖片,接著編輯它的元數(shù)據(jù)。

而實(shí)際更新模型的任務(wù)落到了控制器上面(我們很快就會(huì)講這個(gè)東西)。

讓我們使用vanilla JavaScript 實(shí)現(xiàn)的例子來(lái)更深入的探索一下視圖。下面我們可以看到一個(gè)函數(shù)創(chuàng)建了一個(gè)照片視圖,使用了模型實(shí)例和控制器實(shí)例。

我們?cè)谝晥D里定義了一個(gè)render()工具,使用一個(gè)JavaScript模板引擎來(lái)用于渲染照片模型的內(nèi)容(Underscore的模板),并且更新了我們視圖的內(nèi)容,供照片EI來(lái)參考。

照片模型接著將我們的render()函數(shù)作為一個(gè)其一個(gè)訂閱者的回調(diào)函數(shù),這樣通過(guò)觀察者模式,當(dāng)模型發(fā)生改變的時(shí)候,我們就能觸發(fā)視圖的更新。

人們可能會(huì)問(wèn)用戶交互如何在這里起作用的。當(dāng)用戶點(diǎn)擊視圖中的任何元素,不是由視圖決定接下來(lái)怎么做。而是由控制器為視圖做決定。在我們的例子中,通過(guò)為photoEI增加一個(gè)事件監(jiān)聽器,來(lái)達(dá)到這個(gè)目的,photoEI將會(huì)代理處理送往控制器的點(diǎn)擊行為,在需要的時(shí)候?qū)⒛P托畔⒑褪录徊鬟f。

這個(gè)架構(gòu)的好處是每個(gè)組件在應(yīng)用工作的時(shí)候都扮演著必要的獨(dú)立的角色。

var buildPhotoView = function ( photoModel, photoController ) {

  var base = document.createElement( "div" ),
      photoEl = document.createElement( "div" );

  base.appendChild(photoEl);

  var render = function () {
          // We use a templating library such as Underscore
          // templating which generates the HTML for our
          // photo entry
          photoEl.innerHTML = _.template( "#photoTemplate" , {
              src: photoModel.getSrc()
          });
      };

  photoModel.addSubscriber( render );

  photoEl.addEventListener( "click", function () {
    photoController.handleEvent( "click", photoModel );
  });

  var show = function () {
    photoEl.style.display = "";
  };

  var hide = function () {
    photoEl.style.display = "none";
  };

  return {
    showView: show,
    hideView: hide
  };

};

模板

在支持MVC/MV*的JavaScript框架的下,有必要簡(jiǎn)略的討論一下JavaScript的模板以及它們與視圖之間的關(guān)系,在上一小節(jié),我們已經(jīng)接觸到這種關(guān)系了。

歷史已經(jīng)證明在內(nèi)存中通過(guò)字符串拼接來(lái)構(gòu)建大塊的HTML標(biāo)記是一種糟糕的性能實(shí)踐。開發(fā)者這樣做,就會(huì)深受其害。遍歷數(shù)據(jù),將其封裝成嵌套的div,使用例如document.writeto 這樣過(guò)時(shí)的技術(shù)將"模板"注入到DOM中。這樣通常意味著校本化的標(biāo)記將會(huì)嵌套在我們標(biāo)準(zhǔn)的標(biāo)記中,很快就變得很難閱讀了,更重要的是,維護(hù)這樣的代碼將是一場(chǎng)災(zāi)難,尤其是在構(gòu)建大型應(yīng)用的時(shí)候。

JavaScript 模板解決方案(例如Handlebars.js 和Mustache)通常用于為視圖定義模板作為標(biāo)記(要么存儲(chǔ)在外部,要么存儲(chǔ)在腳本標(biāo)簽里面,使用自定義的類型例如text/template),標(biāo)記中包含有模板變量。變量可以使用變化的語(yǔ)法來(lái)分割(例如{{name}}),框架通常也足夠只能接受JSON格式的數(shù)據(jù)(模型可以轉(zhuǎn)化成JSOn格式),這樣我們只需要關(guān)心如何維護(hù)干凈的模型和干凈的模板。人們?cè)庥龅慕^大多數(shù)的苦差事都被框架本身所處理了。這樣做有大量的好處,尤其選擇是將模板存儲(chǔ)在外部的時(shí)候,這樣在構(gòu)建大型引應(yīng)用的時(shí)候可以是模板按照需要?jiǎng)討B(tài)加載。

下面我們可以看到兩個(gè)HTMP模板的例子。一個(gè)使用流行的Handlebar.js框架實(shí)現(xiàn),一個(gè)使用Underscore模板實(shí)現(xiàn)。

Handlebars.js

<li class="photo">
  <h2>{{caption}}</h2>
  <img class="source" src="{{src}}"/>
  <div class="meta-data">
    {{metadata}}
  </div>
</li>

Underscore.js Microtemplates

<li class="photo">
  <h2><%= caption %></h2>
  <img class="source" src="<%= src %>"/>
  <div class="meta-data">
    <%= metadata %>
  </div>
</li><span style="line-height:1.5;font-family:'sans serif', tahoma, verdana, helvetica;font-size:10pt;"></span>

請(qǐng)注意模板并不是它們自身的視圖,來(lái)自于Struts Model 2 架構(gòu)的開發(fā)者可能會(huì)感覺模板就是一個(gè)視圖,但并不是這樣的。視圖是一個(gè)觀察著模型的對(duì)象,并且讓可視的展現(xiàn)保持最新。模板也許是用一種聲明的方式指定部分甚至所有的視圖對(duì)象,因此它可能是從模板定制文檔生成的。

在經(jīng)典的web開發(fā)中,在單獨(dú)的視圖之間進(jìn)行導(dǎo)航需要利用到頁(yè)面刷新,然而也并不值得這樣做。而在單頁(yè)面Javascript應(yīng)用程序中,一旦數(shù)據(jù)通過(guò)ajax從服務(wù)器端獲取到了,并不需要任何這樣必要的刷新,就可以簡(jiǎn)單的在同一個(gè)頁(yè)面渲染出一個(gè)新的視圖。

這里導(dǎo)航就降級(jí)為了“路由”的角色,用來(lái)輔助管理應(yīng)用程序狀態(tài)(例如,允許用戶用書簽標(biāo)記它們已經(jīng)瀏覽到的視圖)。然而,路由既不是MVC的一部分,也不在每一個(gè)類MVC框架中展現(xiàn)出來(lái),在這一節(jié)中我將不深入詳細(xì)的討論它們。

總而言之,視圖是對(duì)我們的數(shù)據(jù)的一種可視化展現(xiàn)。

控制器

控制器是模型和視圖之間的中介,典型的職責(zé)是當(dāng)用戶操作視圖的時(shí)候同步更新模型。

在我們的照片廊應(yīng)用程序中,控制器會(huì)負(fù)責(zé)處理用戶通過(guò)對(duì)一個(gè)特定照片的視圖進(jìn)行編輯所造成改變,當(dāng)用戶完成編輯后,就更新一個(gè)特定的照片模型。

請(qǐng)記住滿足了MVC中的一種角色:針對(duì)視圖的策略模式的基礎(chǔ)設(shè)施。在策略模式方面,視圖在視圖的自由載量權(quán)方面代表了控制器。因此,那就是測(cè)試模式是如何工作的,視圖可以代表針對(duì)控制器的用戶事件,當(dāng)視圖看起來(lái)合適的時(shí)候。視圖也可以代表針對(duì)控制器的模型變更事件處理,當(dāng)視圖看起來(lái)合適的時(shí)候,但這并不是控制器的傳統(tǒng)角色。

大多數(shù)的Javascript MVC框架都受到了對(duì)"MVC"通常認(rèn)知的影響,而這種認(rèn)知是和控制器綁定在一起的.出現(xiàn)這種情況的原因各異,但在我的真實(shí)想法中,那是由于框架的作者一開始就將從服務(wù)器端的角度看待MVC,意識(shí)到它并不在客戶端進(jìn)行1:1的翻譯,而對(duì)MVC中的C進(jìn)行重新詮釋意在他們感覺更加有意義的事情.與此同在的問(wèn)題在于它是主觀的,增加了理解經(jīng)典MVC模式的復(fù)雜度,當(dāng)然還有控制器在現(xiàn)代框架中的角色。

作為示例,讓我們來(lái)簡(jiǎn)要回顧一下當(dāng)前流行的一種構(gòu)造框架Backbone.js其架構(gòu).Backbone包含了模型和視圖(某些東西同我們前面看到的類似),然而它實(shí)際上并沒(méi)有真正的控制器.它的視圖和路由行為同控制器有一點(diǎn)點(diǎn)類似,但它們自身實(shí)際上都不是控制器。

在這一方面,同官方文檔或者博客文章中可能提到的相左,Backbone既不是一個(gè)真正的MVC/MVP框架,也不是一個(gè)MVVM框架.事實(shí)上把它看做是用它自身的方式架構(gòu)方法的MV*家族中的一員,更加合適.當(dāng)然這沒(méi)有任何錯(cuò)誤的地方,但區(qū)分經(jīng)典MVC和MV*是重要的,我們應(yīng)該依靠前者的經(jīng)典語(yǔ)法來(lái)幫助理解后者。

Spine.js VS Backbone.js

Spine.js

我們現(xiàn)在知道傳統(tǒng)的控制器負(fù)責(zé)當(dāng)用戶更新視圖是同步更新模型.值得注意的一個(gè)有趣的地方是大多數(shù)時(shí)下流行的Javascript MVC/MV*框架在編寫的時(shí)候(Backbone)都沒(méi)有屬于它們自己的明確的控制器的概念。

因此,這對(duì)于我們從另一個(gè)MVC框架中體會(huì)到控制器實(shí)現(xiàn)的差異,并更進(jìn)一步的展現(xiàn)出控制如何扮演著非傳統(tǒng)的角色是很有用處的.對(duì)于這一點(diǎn),讓我們來(lái)看看來(lái)自于Spine.js的示例控制器。

在這個(gè)示例中,我們會(huì)有一個(gè)叫做PhotosController的控制器,用來(lái)管理應(yīng)用程序中的個(gè)人照片.它將確保當(dāng)視圖更新(例如,一個(gè)用戶編輯了照片的元數(shù)據(jù))時(shí),對(duì)應(yīng)的模型也會(huì)更新。

注意:我們并不會(huì)花大力氣研究Spine.js,而只是對(duì)它的控制器能做什么進(jìn)行一定程度的了解:

// Controllers in Spine are created by inheriting from Spine.Controller

var PhotosController = Spine.Controller.sub({ 

  init: function () {
    this.item.bind( "update" , this.proxy( this.render ));
    this.item.bind( "destroy", this.proxy( this.remove ));
  },

  render: function () {
    // Handle templating
    this.replace( $( "#photoTemplate" ).tmpl( this.item ) );
    return this;
  },

  remove: function () {
    this.el.remove();
    this.release();
  }
});

在Spine中,控制器被認(rèn)為是一個(gè)應(yīng)用程序的粘合劑,對(duì)DOM事件進(jìn)行添加和響應(yīng),渲染模板,還有確保視圖和模型保持同步(這在我們所知的控制器的上下文中起作用)。

我們?cè)谏厦娴膃xample.js示例中所做的,是使用render()和remove()方法在更新和銷毀事件中設(shè)置偵聽器。當(dāng)一個(gè)照片條目獲得更新的時(shí)候,我們對(duì)視圖進(jìn)行重新渲染,以此反映對(duì)元數(shù)據(jù)的修改。類似的,如果照片從照片集中被刪除了,我們也會(huì)把它從視圖中移除。在render()函數(shù)中,我們使用Underscore微模板(通過(guò)_.template())來(lái)用ID #photoTemplate對(duì)一個(gè)Javascript模板進(jìn)行渲染。這樣會(huì)簡(jiǎn)單的返回一個(gè)編輯了的HTML字符串用來(lái)填充photoEL的內(nèi)容。

這為我們提供了一個(gè)非常輕量級(jí)的,簡(jiǎn)單的管理模型和視圖之間的變更的方法。

Backbone.js

后面的章節(jié)我們將會(huì)對(duì)Backbone和傳統(tǒng)MVC之間的區(qū)別進(jìn)行一下重新審視,但現(xiàn)在還是讓我們專注于控制器吧。

在Backbone中,控制器的責(zé)任一分為二,由Backbone.View和Backbone.Router共享.前段時(shí)間Backbone確曾有其屬于自己的Backbone.Controller,但是對(duì)這一組件的命名對(duì)于它所被使用的上下文環(huán)境中并沒(méi)有什么意義,后來(lái)它就被重新命名為Router了。

Router比控制器要負(fù)擔(dān)處理著更多一點(diǎn)點(diǎn)的責(zé)任,因?yàn)樗沟脼槟P徒壎ㄊ录?以及讓我們的視圖對(duì)DOM事件和渲染產(chǎn)生響應(yīng),成為可能.如Tim Branyen(另外一名基于Bocoup的Backbone貢獻(xiàn)者)在以前所指出的,為此完全擺脫不使用Backbone.Router是有可能的,因此一種考慮讓它使用Router范式的做法可能像下面這樣:

var PhotoRouter = Backbone.Router.extend({
  routes: { "photos/:id": "route" },

  route: function( id ) {
    var item = photoCollection.get( id );
    var view = new PhotoView( { model: item } );

    $('.content').html( view.render().el );
  }
});

總之,本節(jié)的重點(diǎn)是控制器管理著應(yīng)用程序中模型和視圖之間的邏輯和協(xié)作。

MVC給了我們什么?

MVC中關(guān)注分離的思想有利于對(duì)應(yīng)用程序中功能進(jìn)行更加簡(jiǎn)單的模塊化,并且使得:

  • 整體的維護(hù)更加便利.當(dāng)需要對(duì)應(yīng)用程序進(jìn)行更新時(shí),到底這些改變是否是以數(shù)據(jù)為中心的,意味著對(duì)模型的修改還-有可能是控制器,或者僅僅是視覺的,意味著對(duì)視圖的修改,這一區(qū)分是非常清楚的。
  • 對(duì)模型和視圖的解耦意味著為業(yè)務(wù)邏輯編寫單元測(cè)試將會(huì)是更加直截了當(dāng)?shù)摹?/li>
  • 對(duì)底層模型和控制器的代碼解耦(即我們可能會(huì)取代使用的)在整個(gè)應(yīng)用程序中被淘汰了。
  • 依賴于應(yīng)用程序的體積和角色的分離,這種模塊化允許負(fù)責(zé)核心邏輯的開發(fā)者和工作于用戶界面的開發(fā)者同時(shí)進(jìn)行工作。

JavaScript中的Smalltalk-80 MVC

盡管當(dāng)今主流的JavaScript框架都嘗試引入MVC的模式,來(lái)更好地面對(duì)web應(yīng)用的開發(fā)。由Peter Michaux編寫的Maria.js ,是一個(gè)嘗試純正的Smalltalk-80的框架。其中,Model只是Model,View也只完成View應(yīng)該做的,controller則只負(fù)責(zé)控制。然后,一些開發(fā)人員認(rèn)為,MV*架構(gòu)更值得關(guān)注,如果你對(duì)純正的MVC架構(gòu)的JavaScript實(shí)現(xiàn)感興趣,這將是很好的參考。

更加深入的鉆研

在這本書的這一點(diǎn)上,我們應(yīng)該對(duì)MVC模式提供了些什么有了一個(gè)基礎(chǔ)的了解,然而仍然有一些值得去關(guān)注的非常美妙的信息。

GoF并不將MVC引述為一種設(shè)計(jì)模式,而是把它看做是構(gòu)建一個(gè)用戶界面的類的集合.按照他們的觀點(diǎn),它實(shí)際上是三種經(jīng)典設(shè)計(jì)模式的變異組合:觀察者模式,策略模式和組件模式.依賴于框架中的MVC如何實(shí)現(xiàn),它也可能會(huì)使用工廠和模板模式.GoF Book提到這些模式在使用MVC工作時(shí)是非常有用的附加功能。

如我們所討論的,模型代表應(yīng)用程序的數(shù)據(jù),而視圖則是用戶在屏幕上看到的被展現(xiàn)出來(lái)的東西.如此,MVC它的一些核心的通訊就要依賴于觀察者模式(令人驚奇的是,一些相關(guān)的內(nèi)容在許多關(guān)于MVC模式的書籍并沒(méi)有被涵蓋到).當(dāng)模型被改變時(shí),它會(huì)通知觀察者(視圖)一些東西已經(jīng)被更新了——這也許是MVC中最重要的關(guān)系。觀察者的這一特性也是實(shí)現(xiàn)將多個(gè)視圖連結(jié)到同一個(gè)模型的基礎(chǔ)。

對(duì)于那些對(duì)MVC解耦特性想了解更多的開發(fā)者(這再一次依賴于特定的實(shí)現(xiàn)),這一模式的目標(biāo)之一就是幫助去實(shí)現(xiàn)一個(gè)主體(數(shù)據(jù)對(duì)象)和它的觀察者之間的一對(duì)多關(guān)系的定義。當(dāng)一個(gè)主體發(fā)生改變的時(shí)候,它的觀察者也會(huì)被更新。視圖和控制器有一種稍微不同的關(guān)系.控制器協(xié)助視圖對(duì)不同的用戶輸入做出響應(yīng),這也是一個(gè)策略模式的例子。

總結(jié)

回顧完經(jīng)典的MVC模式以后,我們現(xiàn)在應(yīng)該理解了它是如何允許我們對(duì)一個(gè)應(yīng)用程序中的各個(gè)關(guān)注點(diǎn)進(jìn)行清晰地的區(qū)分.我們現(xiàn)在也應(yīng)該感恩于Javascript MVC框架在它們對(duì)MVC模式的詮釋中是如何的不同,而其對(duì)變異也是相當(dāng)開放的,仍然分享著其原生模式已經(jīng)提供的其中一些基礎(chǔ)概念。

當(dāng)審視一個(gè)新的Javas MVC/MV*框架時(shí),請(qǐng)記住——回過(guò)頭去考察考察它如何選擇相近的架構(gòu)(特別的,它支持實(shí)現(xiàn)了模型,視圖,控制器或者其它的一些可選特性)可能會(huì)有些用處,因?yàn)檫@樣能夠更好的幫助我們深入了解這一框架預(yù)計(jì)需要被如何拿來(lái)使用。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)