說 明:作者也沒寫過什麼框架,只是分享一些自己的理解,拋磚引玉罷了。如果你寫過一些框架可能會產生一些共鳴歡迎討論,如果你正在寫或正打算寫一個框架可能 會給你一些啟發。本文以為較長可能會分多個篇博客來寫,現在能想到的是主要分為步驟、模式兩部分。如果你覺得好,按一個推薦舉手之勞讓更多的人可以看到。 步驟 ...
說 明:作者也沒寫過什麼框架,只是分享一些自己的理解,拋磚引玉罷了。如果你寫過一些框架可能會產生一些共鳴歡迎討論,如果你正在寫或正打算寫一個框架可能 會給你一些啟發。本文以為較長可能會分多個篇博客來寫,現在能想到的是主要分為步驟、模式兩部分。如果你覺得好,按一個推薦舉手之勞讓更多的人可以看到。
步驟
定位
所謂定位就是回答幾個問題,我出於什麼目的要寫一個框架,我的這個框架是乾什麼的,有什麼特性適用於什麼場景,我的這個框架的用戶對象是誰,他們會怎麼使用,框架由誰維護將來怎麼發展等等。
- 如果你打算寫框架,那麼肯定心裡已經有一個初步的定位,比如它是一個緩存框架、Web MVC框架、IOC框架、ORM/數據訪問框架、RPC框架或是一個用於Web開發的全棧式框架。
- 是否要重覆造輪子?除非是練手項目,一般我們是有瞭解決不了問 題的時候才會考慮不使用既有的成熟的框架而重覆造輪子的,這個時候需要列出新框架主要希望解決什麼問題。有關是否應該重覆造輪子的話題討論了很多,我的建 議是在把問題列清後進行簡單的研究看看是否可以通過擴展現有的框架來解決這個問題。一般而言大部分成熟的框架都有一定的擴展和內部組件的替換能力,可以解 決大部分技術問題,但在如下情況下我們可能不得不自己去寫一個框架,比如即使通過擴展也無法滿足技術需求、安全原因、需要更高的生產力、需要讓框架和公司 內部的流程更好地進行適配、開源的普適框架無法滿足性能需求、二次開發的成本高於重新開發的成本等等。
- 主打輕量級?輕量級是很多人打算自己寫一個新框架的原因,但我 們要明白,大部分項目在一開始的時候其實都是輕量級的,隨著框架的用戶越來越多,它必定需要滿足各種奇怪的需求,在經過了無數次迭代之後,框架的主線流程 就會多很多擴展點、檢測點,這樣框架勢必變得越來越重(從框架的入口到框架的工作結束的方法調用層次越來越多,勢必框架也就越來越慢),如果你打算把框架 定位於一個輕量級的框架的話,那麼在今後的迭代過程中需要進行一些權衡,在心中有堅定的輕量級的理念的同時不斷做性能測試來確保框架的輕量,否則隨著時間 的發展框架可能會越來越重進而偏離了開始的定位。
- 特性?如果你打算寫一個框架,並且只有輕量級這一個理由的話,你或許應該再為自己的框架想一些新特性,就像做一個產品一樣,如果找不出兩個以上的亮點,那麼這個產品不太可能成功,比如你的新框架可以是一個零配置的框架,可以是一個前端開發也能用的後端框架。
- 其它?一般來說框架是給程式員使用的,我們要考慮框架使用的頻度是怎麼樣的,這可能決定的框架的性能需求和穩定性需求。還有,需要考慮框架將來怎麼發展,是希望走開源路線還是商業路線。當然,這些問題也可以留到框架有一個大致的結構後再去考慮。
我們來為本文模擬一個場景,假設我們覺得現有的Spring MVC等框架開發起來效率有點低,打算重覆造輪子,對於新框架的定位是一個給Java程式員使用的輕量級的、零配置的、易用的、易擴展的Web MVC框架。
調研
雖然到這裡你已經決定去寫一個框架了,但是在著手寫之前還是至少建議評估一下市面上的類似(成熟)框架。需要做的是通讀這些框架的文檔以及閱讀一些源碼,這麼做有幾個目的:
- 通過分析現有框架的功能,可以制定出一個新框架要實現的功能列表。
- 通過分析現有框架的問題,總結出新框架需要避免的東西和改善的地方。
- 通過閱讀現有框架的源碼,幫助自己理清框架的主線流程為總體設計做鋪墊(後面總體設計部分會更多談到)。
- 如果能充分理解現有的框架,那麼你就是站在巨人的肩膀上寫框架,否則很可能就是在井底造輪子。
新開發一個框架的好處是沒有相容歷史版本的包袱,但是責任也同樣 重大,因為如果對於一開始的定位或設計工作沒有做好的話,將來如果要對格局進行改變就會有巨大的向前相容的包袱(除非你的框架沒有在任何正式項目中使 用),相容意味著框架可能會越來越重,可能會越來越難看,閱讀至少一到兩個開源實現,做好充分的調研工作可以使你避免犯大錯。
假設我們評估了一些主流框架後已經很明確,我們的MVC框架是一個Java平臺的、基於Servlet的輕量級的Web MVC框架,主要的理念是約定優於配置,高內聚大於低耦合,提供主流Web MVC框架的大部分功能,並且易用方面有所創新,新特性體包括:
- 起手零配置,總體上約定由於配置,即使需要擴展配置也支持通過代碼和配置文件兩種方式進行配置。
- 除了Servlet之外不依賴其它類庫,支持通過插件方式和諸如Spring等框架進行整合。
- 更優化的項目結構,不需要按照傳統的Java Web項目結構那樣來分離代碼和WEB-INF,視圖可以和代碼在一起,閱讀代碼更便利。
- 攔截器和框架本身更緊密,提供Action、Controller和Global三個級別的"攔截器"(或者說過濾器)。
- 豐富的Action的返回值,返回的可以是視圖、可以是重定向、可以是文件、可以是字元串、可以是Json數據,可以是Javascript代碼等等。
- 支持針對測試環境自動生成測試的視圖模型數據,以便前端和後端可以同時開發項目。
- 支持在開發的時候自動生成路由信息、模型綁定、異常處理等配置的信息頁面和調試頁面,方便開發和調試。
- 提供一套通用的控制項模版,使得,並且支持多種模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。
嗯,看上去挺誘人的,這是一個不錯的開端,如果你要寫的框架自己都不覺得想用的話,那麼別人就更不會有興趣來嘗試使用你的框架了。
解決難點
之所以把解決難點放在開搞之前是因為,如果實現這個框架的某些特 性,甚至說實現這個框架的主流程有一些核心問題難以解決,那麼就要考慮對框架的特性進行調整,甚至取消框架的開發計划了。有的時候我們在用A平臺的時候發 現一個很好用的框架,希望把這個框架移植到B平臺,這個想法是好的,但之所以在這以前這麼多年沒有人這麼乾過是因為這個平臺的限制壓根不可能實現這樣的東 西。比如我們要實現一個MVC框架,勢必需要依賴平臺提供的反射特性,如果你的語言平臺壓根就沒有運行時反射這個功能,那麼這就是一個非常難以解決的難 點。又比如我們在某個平臺實現一個類似於.NET平臺Linq2Sql的數據訪問框架,但如果這個目標平臺的開發語言並不像C#那樣提供了類型推斷、匿名 類型、Lambda表達式、擴展方法的話那麼由於語法的限制你寫出來的框架在使用的時候是無法像.NET平臺Linq2Sql那樣優雅的,這就違背了實現 框架的主要目的,實現新的框架也就變得意義不大了。
對於我們要實現的MVC框架貌似不存在什麼根本性的無法解決的問 題,畢竟在Java平臺已經有很多可以參考的例子了。如果框架的實現總體上沒什麼問題的話,就需要逐一評估框架的這些新特性是否可以解決。建議對於每一個 難點特性做一個原型項目來證明可行,以免在框架實現到一半的時候發現有無法解決的問題就比較尷尬了。
分析一下,貌似我們要實現的這8大特性只有第1點要研究一下,看看如何免配置通過讓代碼方式讓我們的Web MVC框架可以和Servlet進行整合,如果無法實現的話,我們可能就需要把第1點特性從零配置改為一分鐘快速配置了。
開搞
- 首先需要給自己框架取一個名字,取名要考慮到易讀、易寫、易記,也需要儘量避免和市面上其它產品的名字重覆,還有就是最好不要起一個侮辱其它同類框架的名字以免引起公憤。
- 如果將來打算把項目搞大的話,可以提前註冊一下項目的相關功能變數名稱,畢竟現在功能變數名稱也便宜,避免到時候項目名和功能變數名稱差距很大,或項目的.com或.org功能變數名稱對應了一個什麼不太和諧的網站這就尷尬了。
- 然後就是找一個地方來托管自己的代碼,如果一開始不希望公開代碼的話,最好除了本地源代碼倉庫還有一個異地的倉庫以免磁碟損壞導致抱憾終身,當然如果不怕出醜的話也可以在起步的時候就使用Github等網站來托管自己的代碼。
總體設計
對於總體設計我的建議是一開始不一定需要寫什麼設計文檔畫什麼類 圖,因為可能一開始的時候無法形成這麼具體的概念,我們可以直接從代碼開始做第一步。框架的使用者一般而言還是開發人員,拋開框架的內在的實現不說,框架 的API設計的好壞取決於兩個方面。對於普通開發人員而言就是使用層面的API是否易於使用,拿我們的MVC框架舉例來說:
- 最基本的,搭建一個HelloWorld項目,聲明一個Controller和Action,配置一個路由規則讓Get方法的請求可以解析到這個Action,可以輸出HelloWorld文字,怎麼實現?
- 如果要實現從Cookie以及表單中獲取相關數據綁定到Action的參數裡面,怎麼實現?
- 如果要配置一個Action在調用前需要判斷許可權,在調用後需要記錄日誌,怎麼實現?
我們這裡說的API,它不一定全都是方法調用的API,廣義上來說我們認為框架提供的接入層的使用都可以認為是API,所以上面的一些功能都可以認為是MVC框架的API。
框架除了提供基本的功能,還要提供一定程度的擴展功能,使得一些複雜的項目能夠在某些方面對框架進行增強以適應各種需求,比如:
- 我的Action是否可以返回圖片驗證碼?
- 我的Action的參數綁定是否可以從Memcached中獲取數據?
- 如果出現異常,能否在開發的時候顯示具體的錯誤信息,在正式環境顯示友好的錯誤頁面並且記錄錯誤信息到資料庫?
一般而言如果要實現這樣的功能就需要自己實現框架公開的一些類或 介面,然後把自己的實現"註冊"到框架中,讓框架可以在某個時候去使用這些新的實現。這就需要框架的設計者來考慮應該以怎麼樣的友好形式公開出去哪些內 容,使得以後的擴展實現在自由度以及最少實現上的平衡,同時要兼顧外來的實現不破壞框架已有的結構。
要想清楚這些不是一件容易的事情,所以在框架的設計階段完全可以 使用從上到下的方式進行設計。也就是不去考慮框架怎麼實現,而是以一個使用者的身份來寫一個框架的示例網站,API怎麼簡單怎麼舒服就怎麼設計,只從使用 者的角度來考慮問題。對於相關用到的類,直接寫一個空的類(能用介面的儘量用介面,你的目的只是通過編譯而不是能運行起來),讓程式可以通過編譯就可以 了。你可以從框架的普通使用開始寫這樣一個示例網站,然後再寫各種擴展應用,在此期間你可能會用到框架內部的20個類,這些類就是框架的接入類,在你的示 例網站通過編譯的那剎那,其實你已經實現了框架的接入層的設計。
這裡值得一說的是API的設計蘊含了非常多的學問以及經驗,要在目標平臺設計一套合理易用的API首先需要對目標平臺足夠瞭解,每一個平臺都有一些約定俗成的規範,如果設計的API能符合這些規範那麼開發人員會更容易接受這個框架,此外還有一些建議:
- 之所以我們把API的設計先行,而不是讓框架的設計先行是因為 這樣我們更容易設計出好用的API,作為框架的實現者,我們往往會進行一些妥協,我們可能會為了在框架內部DRY而設計出一套醜陋的API讓框架的使用者 去做一些重覆的工作;我們也可能會因為想讓框架變得更松耦合強迫框架的使用者去使用到框架的一些內部API去初始化框架的組件。如果框架不是易用的,那麼 框架的內部設計的再合理又有什麼意義?
- 儘量少暴露一些框架內部的類名吧,對於框架的使用者來說,你的 框架對他一點都不熟悉,如果要上手你的框架需要學習一到兩個類尚可接受,如果要使用到十幾個類會頭暈腦脹的,即使你的框架有非常多的功能以及配置,可以考 慮提供一個入口類,比如創建一個ConfigCenter類作為入口,讓使用者可以僅僅探索這個類便可對框架進行所有的配置。
- 一個好的框架是可以讓使用者少犯錯誤的,框架的設計者務必要考 慮到,框架的使用者沒有這個業務來按照框架的最佳實踐來做,所以在設計API的時候,如果你希望API的使用者一定要按照某個方式來做的話,可以考慮設置 一個簡便的重載來載入預設的最合理的使用方式而不是要求使用者來為你的方法初始一些什麼依賴,同時也可以在API內部做一些檢測,如果發現開發人員可能會 犯錯進行一些提示或拋出異常。好的框架無需過多的文檔,它可以在開發人員用的時候告知它哪裡錯了,最佳實踐是什麼,即便他們真的錯了也能以預設的更合理的 方式來彌補這個錯誤。
- 建議所有的API都有一套統一的規範,比如入口都叫 XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和ZZZService。API往往需要進行迭代和改良 的,在首個版本中把好名字用掉也不一定是一個好辦法,最好還是給自己的框架各種API的名字留一點餘地,這樣以後萬一需要升級換代不至於太牽強。
下一步工作就是把項目中那些空的類按照功能進行劃分。目的很簡 單,就是讓你的框架的100個類或介面能夠按照功能進行拆分和歸類,這樣別人一打開你的框架就可以馬上知道你的框架分為哪幾個主要部分,而不是在100個 類中暈眩;還有因為一旦在你的框架有使用者後你再要為API相關的那些類調整包就比困難了,即使你在創建框架的時候覺得我的框架就那麼十幾個類無需進行過 多的分類,但是在將來框架變大又發現當初設計的不合理,無法進行結構調整就會變得很痛苦。因此這個工作還是相當重要的,對於大多數框架來說,可以有幾種切 蛋糕的方式:
- 分層。我覺得框架和應用程式 一樣,也需要進行分層。傳統的應用程式我們分為表現層、邏輯層和數據訪問層,類似的對於很多框架也可以進行橫向的層次劃分。要分層的原因是我們的框架要處 理的問題是基於多層抽象的,就像如果沒有OSI七層模型,要讓一個HTTP應用去直接處理網路信號是不合理的也是不利於重用的。舉一個例子,如果我們要寫 一個基於Socket的RPC的框架,我們需要處理方法的代理以及序列化,以及序列化數據的傳輸,這完全是兩個層面的問題,前者偏向於應用層,後者偏向於 網路層,我們完全有理由把我們的框架分為兩個層面的項目(至少是兩個包),rpc.core和rpc.socket,前者不關心網路實現來處理所有RPC 的功能,後者不關心RPC來處理所有的Socket功能,在將來即使我們要淘汰我們的RPC的協議了,我們也可以重用rpc.socket項目,因為它和 RPC的實現沒有任何關係,它關註的只是socket層面的東西。
- 橫切。剛纔說的分層是橫向的 分割,橫切是縱向的分割(橫切是跨多個模塊的意思,不是橫向來切的意思)。其實橫切關註點就是諸如日誌、配置、緩存、AOP、IOC等通用的功能,對於這 部分功能,我們不應該把他們和真正的業務邏輯混淆在一起。對於應用類項目是這樣,對於框架類項目也是這樣,如果某一部分的代碼量非常大,完全有理由為它分 出一個單獨的包。對於RPC項目,我們可能就會把客戶端和服務端通訊的消息放在common包內,把配置的處理單獨放在config包內。
- 功能。也就是要實現一個框架主要解決的問題點,比如對於上面提 到的RPC框架的core部分,可以想到的是我們主要解決是客戶端如何找到服務端,如何把進行方法調用以及把方法的調用信息傳給目標服務端,服務端如何接 受到這樣的信息根據配置在本地實例化對象調用方法後把結果返回客戶端三大問題,那麼我們可能會把項目分為routing、client、server等幾 個包。
如果是一個RPC框架,大概是這樣的結構:
對於我們的Web MVC框架,舉例如下:
-
我們可以有一個mvc.core項目,細分如下的包:
- common:公共的一組件,下麵的各模塊都會用到
- config:配置模塊,解決框架的配置問題
- startup:啟動模塊,解決框架和Servlet如何進行整合的問題
- plugin:插件模塊,插件機制的實現,提供IPlugin的抽象實現
- routing:路由模塊,解決請求路徑的解析問題,提供了IRoute的抽象實現和基本實現
- controller:控制器模塊,解決的是如何產生控制器
- model:視圖模型模塊,解決的是如何綁定方法的參數
- action:action模塊,解決的是如何調用方法以及方法返回的結果,提供了IActionResult的抽象實現和基本實現
- view:視圖模塊,解決的是各種視圖引擎和框架的適配
- filter:過濾器模塊,解決是執行Action,返回IActionResult前後的AOP功能,提供了IFilter的抽象實現以及基本實現
-
我們可以再創建一個mvc.extension項目,細分如下的包:
- filters:一些IFilter的實現
- results:一些IActionResult的實現
- routes:一些IRoute的實現
- plugins:一些IPlugin的實現
這裡我們以IXXX來描述一個抽象,可以是介面也可以是抽象類,在具體實現的時候根據需求再來確定。
這種結構的劃分方式完全吻合上面說的切蛋糕方式,可以看到除了橫 切部分和分層部分,作為一個Web MVC框架,它核心的組件就是routing、model、view、controller、action(當然,對於有些MVC框架它沒有route部 分,route部分是交由Web框架實現的)。
如果我們在這個時候還無法確定框架的模塊劃分的話,問題也不大,我們可以在後續的搭建龍骨的步驟中隨著更多的類的建立,繼續理清和確定模塊的劃分。
經過了設計的步驟,我們應該心裡對下麵的問題有一個初步的規划了:
- 我們的框架以什麼形式來提供如何優雅的API?
- 我們的框架包含哪些模塊,模塊大概的作用是什麼?
搭建龍骨
在經過了初步的設計之後,我們可以考慮為框架搭建一套龍骨,一套 抽象的層次關係。也就是用抽象類、介面或空的類實現框架,可以通過編譯,讓框架撐起來,就像造房子搭建房子的鋼筋混凝土結構(添磚加瓦是後面的事情,我們 先要有一個結構)。對於開發應用程式來說,其實沒有什麼撐起來一說,因為應用程式中很多模塊都是並行的,它可能並沒有一個主結構,主流程,而對於框架來 說,它往往是一個高度面向對象的,高度抽象的一套程式,搭建龍骨也就是搭建一套抽象層。這麼說可能有點抽象,我們還是來想一下如果要做一個Web MVC框架,需要怎麼為上面說的幾個核心模塊進行抽象(我們也來體會一下框架中一些類的命名,這裡我們為了更清晰,為所有介面都命名為IXXX,這點不太 符合Java的命名規範):
-
routing MVC的入口是路由
- 每一個路由都是IRoute代表了不同的路由實現,它也提供一個getRouteResult()方法來返回RouteResult對象
- 我們實現一個框架自帶的DefaultRoute,使得路由支持配置,支持預設值,支持正則表達式,支持約束等等
- 我們需要有一個Routes類來管理所有的路由IRoute,提供一個findRoute()方法來返回RouteResult對象,自然我們這邊調用的就是IRoute的getRouteResult()方法,返回能匹配到的結果
- RouteResult對象就是匹配的路由信息,包含了路由解析後的所有數據
-
controller 路由下來是控制器
- 我們有IControllerFactory來創建Controller,提供createController()方法來返回IController
- IController代表控制器,提供一個execute()方法來執行控制器
- 我們實現一個框架自帶的DefaultControllerFactory來以約定由於配置的方式根據約定規則以及路由數據RouteResult來找到IController並創建它
- 我們為IController提供一個抽象實 現,AbstractController,要求所有MVC框架的使用者創建的控制器需要繼承AbstractController,在這個抽象實現中我 們可以編寫一些便捷的API以便開發人員使用,比如view()方法、file()方法、redirect()方法、json()方法、js()方法等等
-
action 找到了控制器後就是來找要執行的方法了
- 我們有IActionResult來代表Action返回的結果,提供一個execute()方法來執行這個結果
- 我們的框架需要實現一些自帶的IActionResult,比如ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對應AbstractController的一些便捷方法
- 再來定義一個IActionInvoker來執行Action,提供一個invokeAction()方法
- 我們需要實現一個DefaultActionInvoker以預設的方式進行方法的調用,也就是找到方法的一些IFilter按照一定的順序執行他們,最後使用反射進行方法的調用得到上面說的IActionResult並執行它的execute()方法
-
filter 我們的框架很重要的一點就是便捷的過濾器
- 剛纔提到了IFilter,代表的是一個過濾器,我們提供IActionFilter對方法的執行前後進行過濾,提供IResultFilter對IActionResult執行前後進行過濾
- 我們的IActionInvoker怎麼找到需要執行的IFilter呢,我們需要定義一個IFilterProvider來提供過濾器,它提供一個getFilters()方法來提供所有的IFilter的實例
- 我們的框架可以實現一些自帶的 IFilterProvider,比如AnnotationFilterProvider通過掃描Action或Controller上的註解來獲取需要 執行的過濾器信息;比如我們還可以實現GlobalFilterProvider,開發人員可以直接通過配置或代碼方式告知框架應用於全局的 IFilter
- 既然我們實現了多個IFilterProvider,我們自然需要有一個類來管理這些IFilterProvider,我們實現一個FilterProviders類並提供getFilters()方法(這和我們的Routes類來管理IRoute是類似的,命名統一)
-
view 各種IActionResult中最特殊最複雜的就是ViewResult,我們需要有一個單獨的包來處理ViewResult的邏輯
- 我們需要有IViewEngine來代表一個模版引擎,提供一個getViewEngineResult()方法返回ViewEngineResult
- ViewEngineResult包含視圖引擎尋找視圖的結果信息,裡面包含IView和尋找的一些路徑等
- IView自然代表的是一個視圖,提供render()方法(或者為了統一也可以叫做execute)來渲染視圖
- 我們的框架可以實現常見的一些模版引擎,比如 FreemarkerViewEngine、VelocityViewEngine等,VelocityViewEngine返回的 ViewEngineResult自然包含的是一個實現IView的VelocityView,不會返回其它引擎的IView
- 同樣的,我們是不是需要一個ViewEngines來管理所有的IViewEngine呢,同樣也是實現findViewEngine()方法
-
common 這裡可以放一些項目中各個模塊都要用到的一些東西
- 比如各種context,context代表的是執行某個任務 需要的環境信息,這裡我們可以定義HttpContext、ControllerContext、ActionContext和ViewContext, 後者繼承前者,隨著MVC處理流程的進行,View執行時的上下文相比Action執行時的上下文信息肯定是多了視圖的信息,其它同理,之所以把這個信息 放在common裡面而不是放在各個模塊自己的包內是因為這樣更清晰,可以一目瞭然各種對象的執行上下文有一個立體的概念
- 比如各種helper或utility
接下去就不再詳細闡述model、plugin等模塊的內容了。
看到這裡,我們來總結一下,我們的MVC框架在組織結構上有著高度的統一:
- 如果xxx本身並無選擇策略,但xxx的創建過程也不是一個new這麼簡單的,可以由xxxFactory類來提供一個xxx
- 如果我們需要用到很多個yyy,那麼我們會有各種yyyProvider(通過getyyy()方法)來提供這些yyy,並且我們需要有一個yyyProviders來管理這些yyyProvider
- 如果zzz的選擇是有策略性的,會按照需要選擇zzz1或zzzN,那麼我們可能會有一個zzzs來管理這些zzz並且(通過findzzz()方法)來提供合適的zzz
同時我們框架的相關類的命名也是非常統一的,可以一眼看出這是實 現、還是抽象類還是介面;是提供程式,是執行結果還是上下文。當然,在將來的代碼實現過程中很可能會把很多介面變為抽象類提供一些預設的實現,這並不會影 響項目的主結構。我們會在模式篇對框架常用的一些高層設計模式做更多的介紹。
到了這裡,我們的項目里已經有幾十個空的(抽象)類、介面了,其 中也定義了各種方法可以把各個模塊串起來(各種find()方法和execute()方法),可以說整個項目的龍骨已經建立起來了,這種感覺很好,因為我 們心裡很有底,我們只需要在接下去的工作中做兩個事情:
- 實現各種DefaultXXX來走通主流程
- 實現各種IyyyProvider和Izzz介面來完善支線流程
走通主線流程
所謂走通主線流程,就是讓這個框架可以以一個HelloWorld形式跑起來,這就需要把幾個核心類的核心方法使用最簡單的方式進行實現,還是拿我們的MVC框架來舉例子:
-
從startup開始,可能需要實現ServletContextListener來動態註冊我們框架的入口Servlet,暫且起名為DispatcherServlet吧,在這個類中我們需要走一下主線流程
- 調用Routes.findRoute()獲得IRoute
- 調用IRoute.getRouteResult()來獲得RouteResult
- 使用拿到的RouteResult作為參數調用DefaultControllerFactory.createController()獲得IController(其實也是AbstractController)
- 調用IController.execute()
- 在config中創建一個IConfig作為一種配置方式,我 們實現一個DefaultConfig,把各種預設實現註冊到框架中去,也就是DefaultRoute、 DefaultControllerFactory、DefaultActionInvoker,然後把各種IViewEngine加入 ViewEngines
-
然後需要完成相關預設類的實現:
- 實現Routes.findRoute()
- 實現DefaultRoute.getRouteResult()
- 實現DefaultControllerFactory.createController()
- 實現AbstractController.execute()
- 實現DefaultActionInvoker.invokeAction()
- 實現ViewResult.execute()
- 實現ViewEngines.findViewEngine()
- 實現VelocityViewEngine.getViewEngineResult()
- 實現VelocityView.render()
在這一步,我們並不一定要去觸碰filter和model這部分的內容,我們的主線流程只是解析路由,獲得控制器,執行方法,找到視圖然後渲染視圖。過濾器和視圖模型的綁定屬於增強型的功能,屬於支線流程,不屬於主線流程。
雖然在這裡我們說了一些MVC的實現,但本文的目的不在於教你實 現一個MVC框架,所以不用深究每一個類的實現細節,這裡想說的是,在前面的龍骨搭建完後,你會發現按照這個龍骨為它加一點肉上去實現主要的流程是順理成 章的事情,毫無痛苦。在整個實現的過程中,你可以不斷完善common下的一些context,把方法的調用參數封裝到上下文對象中去,不但看起來清楚且 符合開閉原則。到這裡,我們應該可以跑起來在設計階段做的那個示例網站的HelloWorld功能了。
在這裡還想說一點,有些人在實現框架的時候並沒有搭建龍骨的一步驟,直接以非OOP的方式實現了主線流程,這種方式有以下幾個缺點:
- 不容易做到SRP單一指責原則,你很容易把各種邏輯都集中寫在一起,比如大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個框架就肥瘦不勻,有些類特別龐大有些類特別小。
- 不容易做到OCP開閉原則,擴展起來不方便需要修改老的代碼,我們期望的擴展是實現新的類然後讓框架感知,而不是直接修改框架的某些代碼來增強功能。
- 很難實現DIP依賴倒置原則,即使你依賴的確實是IService但其實就沒意義,因為它只有一個實現,只是把他當作幫助類來用罷了。
實現各種支線流程
我們想一下,對於這個MVC框架有哪些沒有實現的支線流程?其實無需多思考,因為我們在搭建龍骨階段的設計已經給了我們明確的方向了,我們只需要把除了主線之外的那些龍骨上也填充一些實體即可,比如:
- 實現更多的IRoute,並註冊到Routes
- 實現更多的IViewEngine,並註冊到ViewEngines
- 實現必要的IFilterProvider以及FilterProviders,把IFilterProvider註冊到FilterProviders
- 增強DefaultActionInvoker.invokeAction()方法,在合適的時候調用這些IFilter
- 實現更多的IActionResult,並且為AbstractController實現更多的便捷方法來返回這些IActionResult
- ……實現更多model模塊的內容和plugin模塊的內容
實現了這一步後,你會發現整個框架飽滿起來了,每一個包中不再是僅有的那些介面和預設實現,而且會有一種OOP的爽快感,爽快感來源於幾個方面:
- 面對介面編程抽象和多態的放心安心的爽快感
- 為抽象類實現具體類享受到父類大量實現的滿足的爽快感
- 實現了大量的介面和抽象類後充實的爽快感
我們再來總結一下之前說的那些內容,實現一個框架的第一大步就是:
- 設計一套合理的介面
- 為框架進行模塊劃分
- 為框架搭建由抽象結構構成的骨架
- 在這個骨架的基礎上實現一個HelloWorld程式
- 為這個骨架的其它部分填充更多實現
經過這樣的一些步驟後可以發現這個框架是很穩固的,很平衡的,很 易於擴展的。其實到這裡很多人覺得框架已經完成了,有血有肉,其實個人覺得只能說開發工作實現了差不多30%,後文會繼續說,畢竟直接把這樣一個血肉之軀 拿出去對外有點嚇人,我們需要為它進行很多包裝和完善。
作者:lovecindywang 轉自:http://www.cnblogs.com/lovecindywang/p/4447739.html 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。