【原創】如何寫一個框架:步驟(上)

 

說明:做者也沒寫過什麼框架,只是分享一些本身的理解,拋磚引玉罷了。若是你寫過一些框架可能會產生一些共鳴歡迎討論,若是你正在寫或正打算寫一個框架可能會給你一些啓發。本文覺得較長可能會分多個篇博客來寫,如今能想到的是主要分爲步驟、模式兩部分。若是你以爲好,按一個推薦舉手之勞讓更多的人能夠看到。html

 

步驟

 

定位

 

所謂定位就是回答幾個問題,我出於什麼目的要寫一個框架,個人這個框架是幹什麼的,有什麼特性適用於什麼場景,個人這個框架的用戶對象是誰,他們會怎麼使用,框架由誰維護未來怎麼發展等等。 前端

  1. 若是你打算寫框架,那麼確定內心已經有一個初步的定位,好比它是一個緩存框架、Web MVC框架、IOC框架、ORM/數據訪問框架、RPC框架或是一個用於Web開發的全棧式框架。
  2. 是否要重複造輪子?除非是練手項目,通常咱們是有了解決不了問題的時候纔會考慮不使用既有的成熟的框架而重複造輪子的,這個時候須要列出新框架主要但願解決什麼問題。有關是否應該重複造輪子的話題討論了不少,個人建議是在把問題列清後進行簡單的研究看看是否能夠經過擴展示有的框架來解決這個問題。通常而言大部分紅熟的框架都有必定的擴展和內部組件的替換能力,能夠解決大部分技術問題,但在以下狀況下咱們可能不得不本身去寫一個框架,好比即便經過擴展也沒法知足技術需求、安全緣由、須要更高的生產力、須要讓框架和公司內部的流程更好地進行適配、開源的普適框架沒法知足性能需求、二次開發的成本高於從新開發的成本等等。
  3. 主打輕量級?輕量級是不少人打算本身寫一個新框架的緣由,但咱們要明白,大部分項目在一開始的時候其實都是輕量級的,隨着框架的用戶愈來愈多,它一定須要知足各類奇怪的需求,在通過了無數次迭代以後,框架的主線流程就會多不少擴展點、檢測點,這樣框架勢必變得愈來愈重(從框架的入口到框架的工做結束的方法調用層次愈來愈多,勢必框架也就愈來愈慢),若是你打算把框架定位於一個輕量級的框架的話,那麼在從此的迭代過程當中須要進行一些權衡,在心中有堅決的輕量級的理念的同時不斷作性能測試來確保框架的輕量,不然隨着時間的發展框架可能會愈來愈重進而偏離了開始的定位。
  4. 特性?若是你打算寫一個框架,而且只有輕量級這一個理由的話,你或許應該再爲本身的框架想一些新特性,就像作一個產品同樣,若是找不出兩個以上的亮點,那麼這個產品不太可能成功,好比你的新框架能夠是一個零配置的框架,能夠是一個前端開發也能用的後端框架。
  5. 其它?通常來講框架是給程序員使用的,咱們要考慮框架使用的頻度是怎麼樣的,這可能決定的框架的性能需求和穩定性需求。還有,須要考慮框架未來怎麼發展,是但願走開源路線仍是商業路線。固然,這些問題也能夠留到框架有一個大體的結構後再去考慮。

咱們來爲本文模擬一個場景,假設咱們以爲現有的Spring MVC等框架開發起來效率有點低,打算重複造輪子,對於新框架的定位是一個給Java程序員使用的輕量級的、零配置的、易用的、易擴展的Web MVC框架。 程序員

 

調研

 

雖然到這裏你已經決定去寫一個框架了,可是在着手寫以前仍是至少建議評估一下市面上的相似(成熟)框架。須要作的是通讀這些框架的文檔以及閱讀一些源碼,這麼作有幾個目的: 正則表達式

  1. 經過分析現有框架的功能,能夠制定出一個新框架要實現的功能列表。
  2. 經過分析現有框架的問題,總結出新框架須要避免的東西和改善的地方。
  3. 經過閱讀現有框架的源碼,幫助本身理清框架的主線流程爲整體設計作鋪墊(後面整體設計部分會更多談到)。
  4. 若是能充分理解現有的框架,那麼你就是站在巨人的肩膀上寫框架,不然極可能就是在井底造輪子。

新開發一個框架的好處是沒有兼容歷史版本的包袱,可是責任也一樣重大,由於若是對於一開始的定位或設計工做沒有作好的話,未來若是要對格局進行改變就會有巨大的向前兼容的包袱(除非你的框架沒有在任何正式項目中使用),兼容意味着框架可能會愈來愈重,可能會愈來愈難看,閱讀至少一到兩個開源實現,作好充分的調研工做可使你避免犯大錯。 數據庫

假設咱們評估了一些主流框架後已經很明確,咱們的MVC框架是一個Java平臺的、基於Servlet的輕量級的Web MVC框架,主要的理念是約定優於配置,高內聚大於低耦合,提供主流Web MVC框架的大部分功能,而且易用方面有所創新,新特性體包括: 編程

  1. 起手零配置,整體上約定因爲配置,即便須要擴展配置也支持經過代碼和配置文件兩種方式進行配置。
  2. 除了Servlet以外不依賴其它類庫,支持經過插件方式和諸如Spring等框架進行整合。
  3. 更優化的項目結構,不須要按照傳統的Java Web項目結構那樣來分離代碼和WEB-INF,視圖能夠和代碼在一塊兒,閱讀代碼更便利。
  4. 攔截器和框架自己更緊密,提供Action、Controller和Global三個級別的"攔截器"(或者說過濾器)。
  5. 豐富的Action的返回值,返回的能夠是視圖、能夠是重定向、能夠是文件、能夠是字符串、能夠是Json數據,能夠是Javascript代碼等等。
  6. 支持針對測試環境自動生成測試的視圖模型數據,以便前端和後端能夠同時開發項目。
  7. 支持在開發的時候自動生成路由信息、模型綁定、異常處理等配置的信息頁面和調試頁面,方便開發和調試。
  8. 提供一套通用的控件模版,使得,而且支持多種模版引擎,好比Jsp、Velocity、Freemarker、Mustache等等。

嗯,看上去挺誘人的,這是一個不錯的開端,若是你要寫的框架本身都不以爲想用的話,那麼別人就更不會有興趣來嘗試使用你的框架了。 json

 

解決難點

 

之因此把解決難點放在開搞以前是由於,若是實現這個框架的某些特性,甚至說實現這個框架的主流程有一些核心問題難以解決,那麼就要考慮對框架的特性進行調整,甚至取消框架的開發計劃了。有的時候咱們在用A平臺的時候發現一個很好用的框架,但願把這個框架移植到B平臺,這個想法是好的,但之因此在這之前這麼多年沒有人這麼幹過是由於這個平臺的限制壓根不可能實現這樣的東西。好比咱們要實現一個MVC框架,勢必須要依賴平臺提供的反射特性,若是你的語言平臺壓根就沒有運行時反射這個功能,那麼這就是一個很是難以解決的難點。又好比咱們在某個平臺實現一個相似於.NET平臺Linq2Sql的數據訪問框架,但若是這個目標平臺的開發語言並不像C#那樣提供了類型推斷、匿名類型、Lambda表達式、擴展方法的話那麼因爲語法的限制你寫出來的框架在使用的時候是沒法像.NET平臺Linq2Sql那樣優雅的,這就違背了實現框架的主要目的,實現新的框架也就變得意義不大了。 後端

對於咱們要實現的MVC框架貌似不存在什麼根本性的沒法解決的問題,畢竟在Java平臺已經有不少能夠參考的例子了。若是框架的實現整體上沒什麼問題的話,就須要逐一評估框架的這些新特性是否能夠解決。建議對於每個難點特性作一個原型項目來證實可行,以避免在框架實現到一半的時候發現有沒法解決的問題就比較尷尬了。 設計模式

分析一下,貌似咱們要實現的這8大特性只有第1點要研究一下,看看如何免配置經過讓代碼方式讓咱們的Web MVC框架能夠和Servlet進行整合,若是沒法實現的話,咱們可能就須要把第1點特性從零配置改成一分鐘快速配置了。 緩存

 

開搞

 

  1. 首先須要給本身框架取一個名字,取名要考慮到易讀、易寫、易記,也須要儘可能避免和市面上其它產品的名字重複,還有就是最好不要起一個侮辱其它同類框架的名字以避免引發公憤。
  2. 若是未來打算把項目搞大的話,能夠提早註冊一下項目的相關域名,畢竟如今域名也便宜,避免到時候項目名和域名差距很大,或項目的.com或.org域名對應了一個什麼不太和諧的網站這就尷尬了。
  3. 而後就是找一個地方來託管本身的代碼,若是一開始不但願公開代碼的話,最好除了本地源代碼倉庫還有一個異地的倉庫以避免磁盤損壞致使抱憾終身,固然若是不怕出醜的話也能夠在起步的時候就使用Github等網站來託管本身的代碼。

 

整體設計

 

對於整體設計個人建議是一開始不必定須要寫什麼設計文檔畫什麼類圖,由於可能一開始的時候沒法造成這麼具體的概念,咱們能夠直接從代碼開始作第一步。框架的使用者通常而言仍是開發人員,拋開框架的內在的實現不說,框架的API設計的好壞取決於兩個方面。對於普通開發人員而言就是使用層面的API是否易於使用,拿咱們的MVC框架舉例來講:

  1. 最基本的,搭建一個HelloWorld項目,聲明一個Controller和Action,配置一個路由規則讓Get方法的請求能夠解析到這個Action,能夠輸出HelloWorld文字,怎麼實現?
  2. 若是要實現從Cookie以及表單中獲取相關數據綁定到Action的參數裏面,怎麼實現?
  3. 若是要配置一個Action在調用前須要判斷權限,在調用後須要記錄日誌,怎麼實現?

咱們這裏說的API,它不必定全都是方法調用的API,廣義上來講咱們認爲框架提供的接入層的使用均可以認爲是API,因此上面的一些功能均可以認爲是MVC框架的API。

框架除了提供基本的功能,還要提供必定程度的擴展功能,使得一些複雜的項目可以在某些方面對框架進行加強以適應各類需求,好比:

  1. 個人Action是否能夠返回圖片驗證碼?
  2. 個人Action的參數綁定是否能夠從Memcached中獲取數據?
  3. 若是出現異常,可否在開發的時候顯示具體的錯誤信息,在正式環境顯示友好的錯誤頁面而且記錄錯誤信息到數據庫?

通常而言若是要實現這樣的功能就須要本身實現框架公開的一些類或接口,而後把本身的實現"註冊"到框架中,讓框架能夠在某個時候去使用這些新的實現。這就須要框架的設計者來考慮應該以怎麼樣的友好形式公開出去哪些內容,使得之後的擴展實如今自由度以及最少實現上的平衡,同時要兼顧外來的實現不破壞框架已有的結構。

要想清楚這些不是一件容易的事情,因此在框架的設計階段徹底可使用從上到下的方式進行設計。也就是不去考慮框架怎麼實現,而是以一個使用者的身份來寫一個框架的示例網站,API怎麼簡單怎麼舒服就怎麼設計,只從使用者的角度來考慮問題。對於相關用到的類,直接寫一個空的類(能用接口的儘可能用接口,你的目的只是經過編譯而不是能運行起來),讓程序能夠經過編譯就能夠了。你能夠從框架的普通使用開始寫這樣一個示例網站,而後再寫各類擴展應用,在此期間你可能會用到框架內部的20個類,這些類就是框架的接入類,在你的示例網站經過編譯的那剎那,其實你已經實現了框架的接入層的設計。

這裏值得一說的是API的設計蘊含了很是多的學問以及經驗,要在目標平臺設計一套合理易用的API首先須要對目標平臺足夠了解,每個平臺都有一些約定俗成的規範,若是設計的API能符合這些規範那麼開發人員會更容易接受這個框架,此外還有一些建議:

  1. 之因此咱們把API的設計先行,而不是讓框架的設計先行是由於這樣咱們更容易設計出好用的API,做爲框架的實現者,咱們每每會進行一些妥協,咱們可能會爲了在框架內部DRY而設計出一套醜陋的API讓框架的使用者去作一些重複的工做;咱們也可能會由於想讓框架變得更鬆耦合強迫框架的使用者去使用到框架的一些內部API去初始化框架的組件。若是框架不是易用的,那麼框架的內部設計的再合理又有什麼意義?
  2. 儘可能少暴露一些框架內部的類名吧,對於框架的使用者來講,你的框架對他一點都不熟悉,若是要上手你的框架須要學習一到兩個類尚可接受,若是要使用到十幾個類會頭暈腦脹的,即便你的框架有很是多的功能以及配置,能夠考慮提供一個入口類,好比建立一個ConfigCenter類做爲入口,讓使用者能夠僅僅探索這個類即可對框架進行全部的配置。
  3. 一個好的框架是可讓使用者少犯錯誤的,框架的設計者務必要考慮到,框架的使用者沒有這個業務來按照框架的最佳實踐來作,因此在設計API的時候,若是你但願API的使用者必定要按照某個方式來作的話,能夠考慮設置一個簡便的重載來加載默認的最合理的使用方式而不是要求使用者來爲你的方法初始一些什麼依賴,同時也能夠在API內部作一些檢測,若是發現開發人員可能會犯錯進行一些提示或拋出異常。好的框架無需過多的文檔,它能夠在開發人員用的時候告知它哪裏錯了,最佳實踐是什麼,即使他們真的錯了也能以默認的更合理的方式來彌補這個錯誤。
  4. 建議全部的API都有一套統一的規範,好比入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和ZZZService。API每每須要進行迭代和改良的,在首個版本中把好名字用掉也不必定是一個好辦法,最好仍是給本身的框架各類API的名字留一點餘地,這樣之後萬一須要升級換代不至於太牽強。

下一步工做就是把項目中那些空的類按照功能進行劃分。目的很簡單,就是讓你的框架的100個類或接口可以按照功能進行拆分和歸類,這樣別人一打開你的框架就能夠立刻知道你的框架分爲哪幾個主要部分,而不是在100個類中暈眩;還有由於一旦在你的框架有使用者後你再要爲API相關的那些類調整包就比困難了,即便你在建立框架的時候以爲個人框架就那麼十幾個類無需進行過多的分類,可是在未來框架變大又發現當初設計的不合理,沒法進行結構調整就會變得很痛苦。所以這個工做仍是至關重要的,對於大多數框架來講,能夠有幾種切蛋糕的方式:

  1. 分層。我以爲框架和應用程序同樣,也須要進行分層。傳統的應用程序咱們分爲表現層、邏輯層和數據訪問層,相似的對於不少框架也能夠進行橫向的層次劃分。要分層的緣由是咱們的框架要處理的問題是基於多層抽象的,就像若是沒有OSI七層模型,要讓一個HTTP應用去直接處理網絡信號是不合理的也是不利於重用的。舉一個例子,若是咱們要寫一個基於Socket的RPC的框架,咱們須要處理方法的代理以及序列化,以及序列化數據的傳輸,這徹底是兩個層面的問題,前者偏向於應用層,後者偏向於網絡層,咱們徹底有理由把咱們的框架分爲兩個層面的項目(至少是兩個包),rpc.core和rpc.socket,前者不關心網絡實現來處理全部RPC的功能,後者不關心RPC來處理全部的Socket功能,在未來即便咱們要淘汰咱們的RPC的協議了,咱們也能夠重用rpc.socket項目,由於它和RPC的實現沒有任何關係,它關注的只是socket層面的東西。
  2. 橫切。剛纔說的分層是橫向的分割,橫切是縱向的分割(橫切是跨多個模塊的意思,不是橫向來切的意思)。其實橫切關注點就是諸如日誌、配置、緩存、AOP、IOC等通用的功能,對於這部分功能,咱們不該該把他們和真正的業務邏輯混淆在一塊兒。對於應用類項目是這樣,對於框架類項目也是這樣,若是某一部分的代碼量很是大,徹底有理由爲它分出一個單獨的包。對於RPC項目,咱們可能就會把客戶端和服務端通信的消息放在common包內,把配置的處理單獨放在config包內。
  3. 功能。也就是要實現一個框架主要解決的問題點,好比對於上面提到的RPC框架的core部分,能夠想到的是咱們主要解決是客戶端如何找到服務端,如何把進行方法調用以及把方法的調用信息傳給目標服務端,服務端如何接受到這樣的信息根據配置在本地實例化對象調用方法後把結果返回客戶端三大問題,那麼咱們可能會把項目分爲routing、client、server等幾個包。

若是是一個RPC框架,大概是這樣的結構:

對於咱們的Web MVC框架,舉例以下:

  1. 咱們能夠有一個mvc.core項目,細分以下的包:
    1. common:公共的一組件,下面的各模塊都會用到
    2. config:配置模塊,解決框架的配置問題
    3. startup:啓動模塊,解決框架和Servlet如何進行整合的問題
    4. plugin:插件模塊,插件機制的實現,提供IPlugin的抽象實現
    5. routing:路由模塊,解決請求路徑的解析問題,提供了IRoute的抽象實現和基本實現
    6. controller:控制器模塊,解決的是如何產生控制器
    7. model:視圖模型模塊,解決的是如何綁定方法的參數
    8. action:action模塊,解決的是如何調用方法以及方法返回的結果,提供了IActionResult的抽象實現和基本實現
    9. view:視圖模塊,解決的是各類視圖引擎和框架的適配
    10. filter:過濾器模塊,解決是執行Action,返回IActionResult先後的AOP功能,提供了IFilter的抽象實現以及基本實現
  2. 咱們能夠再建立一個mvc.extension項目,細分以下的包:
    1. filters:一些IFilter的實現
    2. results:一些IActionResult的實現
    3. routes:一些IRoute的實現
    4. plugins:一些IPlugin的實現

這裏咱們以IXXX來描述一個抽象,能夠是接口也能夠是抽象類,在具體實現的時候根據需求再來肯定。

這種結構的劃分方式徹底吻合上面說的切蛋糕方式,能夠看到除了橫切部分和分層部分,做爲一個Web MVC框架,它核心的組件就是routing、model、view、controller、action(固然,對於有些MVC框架它沒有route部分,route部分是交由Web框架實現的)。

若是咱們在這個時候還沒法肯定框架的模塊劃分的話,問題也不大,咱們能夠在後續的搭建龍骨的步驟中隨着更多的類的創建,繼續理清和肯定模塊的劃分。

通過了設計的步驟,咱們應該內心對下面的問題有一個初步的規劃了:

  1. 咱們的框架以什麼形式來提供如何優雅的API?
  2. 咱們的框架包含哪些模塊,模塊大概的做用是什麼?

 

搭建龍骨

 

在通過了初步的設計以後,咱們能夠考慮爲框架搭建一套龍骨,一套抽象的層次關係。也就是用抽象類、接口或空的類實現框架,能夠經過編譯,讓框架撐起來,就像造房子搭建房子的鋼筋混凝土結構(添磚加瓦是後面的事情,咱們先要有一個結構)。對於開發應用程序來講,其實沒有什麼撐起來一說,由於應用程序中不少模塊都是並行的,它可能並無一個主結構,主流程,而對於框架來講,它每每是一個高度面向對象的,高度抽象的一套程序,搭建龍骨也就是搭建一套抽象層。這麼說可能有點抽象,咱們仍是來想一下若是要作一個Web MVC框架,須要怎麼爲上面說的幾個核心模塊進行抽象(咱們也來體會一下框架中一些類的命名,這裏咱們爲了更清晰,爲全部接口都命名爲IXXX,這點不太符合Java的命名規範):

  1. routing MVC的入口是路由
    1. 每個路由都是IRoute表明了不一樣的路由實現,它也提供一個getRouteResult()方法來返回RouteResult對象
    2. 咱們實現一個框架自帶的DefaultRoute,使得路由支持配置,支持默認值,支持正則表達式,支持約束等等
    3. 咱們須要有一個Routes類來管理全部的路由IRoute,提供一個findRoute()方法來返回RouteResult對象,天然咱們這邊調用的就是IRoute的getRouteResult()方法,返回能匹配到的結果
    4. RouteResult對象就是匹配的路由信息,包含了路由解析後的全部數據
  2. controller 路由下來是控制器
    1. 咱們有IControllerFactory來建立Controller,提供createController()方法來返回IController
    2. IController表明控制器,提供一個execute()方法來執行控制器
    3. 咱們實現一個框架自帶的DefaultControllerFactory來以約定因爲配置的方式根據約定規則以及路由數據RouteResult來找到IController並建立它
    4. 咱們爲IController提供一個抽象實現,AbstractController,要求全部MVC框架的使用者建立的控制器須要繼承AbstractController,在這個抽象實現中咱們能夠編寫一些便捷的API以便開發人員使用,好比view()方法、file()方法、redirect()方法、json()方法、js()方法等等
  3. action 找到了控制器後就是來找要執行的方法了
    1. 咱們有IActionResult來表明Action返回的結果,提供一個execute()方法來執行這個結果
    2. 咱們的框架須要實現一些自帶的IActionResult,好比ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對應AbstractController的一些便捷方法
    3. 再來定義一個IActionInvoker來執行Action,提供一個invokeAction()方法
    4. 咱們須要實現一個DefaultActionInvoker以默認的方式進行方法的調用,也就是找到方法的一些IFilter按照必定的順序執行他們,最後使用反射進行方法的調用獲得上面說的IActionResult並執行它的execute()方法
  4. filter 咱們的框架很重要的一點就是便捷的過濾器
    1. 剛纔提到了IFilter,表明的是一個過濾器,咱們提供IActionFilter對方法的執行先後進行過濾,提供IResultFilter對IActionResult執行先後進行過濾
    2. 咱們的IActionInvoker怎麼找到須要執行的IFilter呢,咱們須要定義一個IFilterProvider來提供過濾器,它提供一個getFilters()方法來提供全部的IFilter的實例
    3. 咱們的框架能夠實現一些自帶的IFilterProvider,好比AnnotationFilterProvider經過掃描Action或Controller上的註解來獲取須要執行的過濾器信息;好比咱們還能夠實現GlobalFilterProvider,開發人員能夠直接經過配置或代碼方式告知框架應用於全局的IFilter
    4. 既然咱們實現了多個IFilterProvider,咱們天然須要有一個類來管理這些IFilterProvider,咱們實現一個FilterProviders類並提供getFilters()方法(這和咱們的Routes類來管理IRoute是相似的,命名統一)
  5. view 各類IActionResult中最特殊最複雜的就是ViewResult,咱們須要有一個單獨的包來處理ViewResult的邏輯
    1. 咱們須要有IViewEngine來表明一個模版引擎,提供一個getViewEngineResult()方法返回ViewEngineResult
    2. ViewEngineResult包含視圖引擎尋找視圖的結果信息,裏面包含IView和尋找的一些路徑等
    3. IView天然表明的是一個視圖,提供render()方法(或者爲了統一也能夠叫作execute)來渲染視圖
    4. 咱們的框架能夠實現常見的一些模版引擎,好比FreemarkerViewEngine、VelocityViewEngine等,VelocityViewEngine返回的ViewEngineResult天然包含的是一個實現IView的VelocityView,不會返回其它引擎的IView
    5. 一樣的,咱們是否是須要一個ViewEngines來管理全部的IViewEngine呢,一樣也是實現findViewEngine()方法
  6. common 這裏能夠放一些項目中各個模塊都要用到的一些東西
    1. 好比各類context,context表明的是執行某個任務須要的環境信息,這裏咱們能夠定義HttpContext、ControllerContext、ActionContext和ViewContext,後者繼承前者,隨着MVC處理流程的進行,View執行時的上下文相比Action執行時的上下文信息確定是多了視圖的信息,其它同理,之因此把這個信息放在common裏面而不是放在各個模塊本身的包內是由於這樣更清晰,能夠一目瞭然各類對象的執行上下文有一個立體的概念
    2. 好比各類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()方法),能夠說整個項目的龍骨已經創建起來了,這種感受很好,由於咱們內心頗有底,咱們只須要在接下去的工做中作兩個事情:

  1. 實現各類DefaultXXX來走通主流程
  2. 實現各類IyyyProvider和Izzz接口來完善支線流程

 

走通主線流程

 

所謂走通主線流程,就是讓這個框架能夠以一個HelloWorld形式跑起來,這就須要把幾個核心類的核心方法使用最簡單的方式進行實現,仍是拿咱們的MVC框架來舉例子:

  1. 從startup開始,可能須要實現ServletContextListener來動態註冊咱們框架的入口Servlet,暫且起名爲DispatcherServlet吧,在這個類中咱們須要走一下主線流程
    1. 調用Routes.findRoute()得到IRoute
    2. 調用IRoute.getRouteResult()來得到RouteResult
    3. 使用拿到的RouteResult做爲參數調用DefaultControllerFactory.createController()得到IController(其實也是AbstractController)
    4. 調用IController.execute()    
  2. 在config中建立一個IConfig做爲一種配置方式,咱們實現一個DefaultConfig,把各類默認實現註冊到框架中去,也就是DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,而後把各類IViewEngine加入ViewEngines
  3. 而後須要完成相關默認類的實現:
    1. 實現Routes.findRoute()
    2. 實現DefaultRoute.getRouteResult()
    3. 實現DefaultControllerFactory.createController()
    4. 實現AbstractController.execute()
    5. 實現DefaultActionInvoker.invokeAction()
    6. 實現ViewResult.execute()
    7. 實現ViewEngines.findViewEngine()
    8. 實現VelocityViewEngine.getViewEngineResult()
    9. 實現VelocityView.render()

在這一步,咱們並不必定要去觸碰filter和model這部分的內容,咱們的主線流程只是解析路由,得到控制器,執行方法,找到視圖而後渲染視圖。過濾器和視圖模型的綁定屬於加強型的功能,屬於支線流程,不屬於主線流程。

雖然在這裏咱們說了一些MVC的實現,但本文的目的不在於教你實現一個MVC框架,因此不用深究每個類的實現細節,這裏想說的是,在前面的龍骨搭建完後,你會發現按照這個龍骨爲它加一點肉上去實現主要的流程是瓜熟蒂落的事情,毫無痛苦。在整個實現的過程當中,你能夠不斷完善common下的一些context,把方法的調用參數封裝到上下文對象中去,不但看起來清楚且符合開閉原則。到這裏,咱們應該能夠跑起來在設計階段作的那個示例網站的HelloWorld功能了。

在這裏還想說一點,有些人在實現框架的時候並無搭建龍骨的一步驟,直接以非OOP的方式實現了主線流程,這種方式有如下幾個缺點:

  1. 不容易作到SRP單一指責原則,你很容易把各類邏輯都集中寫在一塊兒,好比大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個框架就肥瘦不勻,有些類特別龐大有些類特別小。
  2. 不容易作到OCP開閉原則,擴展起來不方便須要修改老的代碼,咱們指望的擴展是實現新的類而後讓框架感知,而不是直接修改框架的某些代碼來加強功能。
  3. 很難實現DIP依賴倒置原則,即便你依賴的確實是IService但其實就沒意義,由於它只有一個實現,只是把他看成幫助類來用罷了。

 

實現各類支線流程

 

咱們想一下,對於這個MVC框架有哪些沒有實現的支線流程?其實無需多思考,由於咱們在搭建龍骨階段的設計已經給了咱們明確的方向了,咱們只須要把除了主線以外的那些龍骨上也填充一些實體便可,好比:

  1. 實現更多的IRoute,並註冊到Routes
  2. 實現更多的IViewEngine,並註冊到ViewEngines
  3. 實現必要的IFilterProvider以及FilterProviders,把IFilterProvider註冊到FilterProviders
  4. 加強DefaultActionInvoker.invokeAction()方法,在合適的時候調用這些IFilter
  5. 實現更多的IActionResult,而且爲AbstractController實現更多的便捷方法來返回這些IActionResult
  6. ……實現更多model模塊的內容和plugin模塊的內容

實現了這一步後,你會發現整個框架飽滿起來了,每個包中再也不是僅有的那些接口和默認實現,並且會有一種OOP的爽快感,爽快感來源於幾個方面:

  1. 面對接口編程抽象和多態的放心安心的爽快感
  2. 爲抽象類實現具體類享受到父類大量實現的知足的爽快感
  3. 實現了大量的接口和抽象類後充實的爽快感

咱們再來總結一下以前說的那些內容,實現一個框架的第一大步就是:

  1. 設計一套合理的接口
  2. 爲框架進行模塊劃分
  3. 爲框架搭建由抽象結構構成的骨架
  4. 在這個骨架的基礎上實現一個HelloWorld程序
  5. 爲這個骨架的其它部分填充更多實現

通過這樣的一些步驟後能夠發現這個框架是很穩固的,很平衡的,很易於擴展的。其實到這裏不少人以爲框架已經完成了,有血有肉,其實我的以爲只能說開發工做實現了差很少30%,後文會繼續說,畢竟直接把這樣一個血肉之軀拿出去對外有點嚇人,咱們須要爲它進行不少包裝和完善。

 【原創】如何寫一個框架:步驟(下)

相關文章
相關標籤/搜索