日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

你知道如何写一个框架吗?详细步骤放送(上)

發(fā)布時間:2024/1/18 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你知道如何写一个框架吗?详细步骤放送(上) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

點擊上面  免費訂閱本賬號!

本公眾號主要推送javaweb開發(fā)相關(guān)技術(shù),基礎(chǔ)知識點,同時會深入剖析復(fù)雜的問題,分享一些優(yōu)秀的框架,大型項目經(jīng)驗,當(dāng)今最流行的Javaweb技術(shù),熱點科技新聞,招聘信息,生活樂趣等等。點擊上方的藍字,這樣您每天可以看到更多的java知識和資訊!完全是免費訂閱,請放心關(guān)注。

作者也沒寫過什么框架,只是分享一些自己的理解,拋磚引玉罷了。如果你寫過一些框架可能會產(chǎn)生一些共鳴歡迎討論,如果你正在寫或正打算寫一個框架可能會給你一些啟發(fā)。本文以為較長可能會分多個篇博客來寫,現(xiàn)在能想到的是主要分為步驟、模式兩部分。如果你覺得好,按一個推薦舉手之勞讓更多的人可以看到。寫本文的時候作者完全是把腦子里的東西寫了出來,沒有參考任何的資料,所以對于每一項內(nèi)容可能都是不完整的,不能作為一個完整的參考。有一些方法學(xué)的東西每個人都有自己的喜好,沒有覺得的對和錯。

定位

所謂定位就是回答幾個問題,我出于什么目的要寫一個框架,我的這個框架是干什么的,有什么特性適用于什么場景,我的這個框架的用戶對象是誰,他們會怎么使用,框架由誰維護將來怎么發(fā)展等等。

  • 如果你打算寫框架,那么肯定心里已經(jīng)有一個初步的定位,比如它是一個緩存框架、Web MVC框架、IOC框架、ORM/數(shù)據(jù)訪問框架、RPC框架或是一個用于Web開發(fā)的全棧式框架。

  • 是 否要重復(fù)造輪子?除非是練手項目,一般我們是有了解決不了問題的時候才會考慮不使用既有的成熟的框架而重復(fù)造輪子的,這個時候需要列出新框架主要希望解決 什么問題。有關(guān)是否應(yīng)該重復(fù)造輪子的話題討論了很多,我的建議是在把問題列清后進行簡單的研究看看是否可以通過擴展現(xiàn)有的框架來解決這個問題。一般而言大 部分成熟的框架都有一定的擴展和內(nèi)部組件的替換能力,可以解決大部分技術(shù)問題,但在如下情況下我們可能不得不自己去寫一個框架,比如即使通過擴展也無法滿 足技術(shù)需求、安全原因、需要更高的生產(chǎn)力、需要讓框架和公司內(nèi)部的流程更好地進行適配、開源的普適框架無法滿足性能需求、二次開發(fā)的成本高于重新開發(fā)的成 本等等。

  • 主打輕量級?輕量級是很多人打算自己寫一個新框架的原因,但我們要明白,大部分項目在一開始的時候其實都是輕量級的,隨著框架 的用戶越來越多,它必定需要滿足各種奇怪的需求,在經(jīng)過了無數(shù)次迭代之后,框架的主線流程就會多很多擴展點、檢測點,這樣框架勢必變得越來越重(從框架的 入口到框架的工作結(jié)束的方法調(diào)用層次越來越多,勢必框架也就越來越慢),如果你打算把框架定位于一個輕量級的框架的話,那么在今后的迭代過程中需要進行一 些權(quán)衡,在心中有堅定的輕量級的理念的同時不斷做性能測試來確??蚣艿妮p量,否則隨著時間的發(fā)展框架可能會越來越重進而偏離了開始的定位。

  • 特性?如果你打算寫一個框架,并且只有輕量級這一個理由的話,你或許應(yīng)該再為自己的框架想一些新特性,就像做一個產(chǎn)品一樣,如果找不出兩個以上的亮點,那么這個產(chǎn)品不太可能成功,比如你的新框架可以是一個零配置的框架,可以是一個前端開發(fā)也能用的后端框架。

  • 其它?一般來說框架是給程序員使用的,我們要考慮框架使用的頻度是怎么樣的,這可能決定的框架的性能需求和穩(wěn)定性需求。還有,需要考慮框架將來怎么發(fā)展,是希望走開源路線還是商業(yè)路線。當(dāng)然,這些問題也可以留到框架有一個大致的結(jié)構(gòu)后再去考慮。

  • 我們來為本文模擬一個場景,假設(shè)我們覺得現(xiàn)有的Spring MVC等框架開發(fā)起來效率有點低,打算重復(fù)造輪子,對于新框架的定位是一個給Java程序員使用的輕量級的、零配置的、易用的、易擴展的Web MVC框架。


    調(diào)研

    雖然到這里你已經(jīng)決定去寫一個框架了,但是在著手寫之前還是至少建議評估一下市面上的類似(成熟)框架。需要做的是通讀這些框架的文檔以及閱讀一些源碼,這么做有幾個目的:

  • 通過分析現(xiàn)有框架的功能,可以制定出一個新框架要實現(xiàn)的功能列表。

  • 通過分析現(xiàn)有框架的問題,總結(jié)出新框架需要避免的東西和改善的地方。

  • 通過閱讀現(xiàn)有框架的源碼,幫助自己理清框架的主線流程為總體設(shè)計做鋪墊(后面總體設(shè)計部分會更多談到)。

  • 如果能充分理解現(xiàn)有的框架,那么你就是站在巨人的肩膀上寫框架,否則很可能就是在井底造輪子。

  • 新 開發(fā)一個框架的好處是沒有兼容歷史版本的包袱,但是責(zé)任也同樣重大,因為如果對于一開始的定位或設(shè)計工作沒有做好的話,將來如果要對格局進行改變就會有巨 大的向前兼容的包袱(除非你的框架沒有在任何正式項目中使用),兼容意味著框架可能會越來越重,可能會越來越難看,閱讀至少一到兩個開源實現(xiàn),做好充分的 調(diào)研工作可以使你避免犯大錯。

    假設(shè)我們評估了一些主流框架后已經(jīng)很明確,我們的MVC框架是一個Java平臺的、基于Servlet的輕量級的Web MVC框架,主要的理念是約定優(yōu)于配置,高內(nèi)聚大于低耦合,提供主流Web MVC框架的大部分功能,并且易用方面有所創(chuàng)新,新特性體包括:

  • 起手零配置,總體上約定由于配置,即使需要擴展配置也支持通過代碼和配置文件兩種方式進行配置。

  • 除了Servlet之外不依賴其它類庫,支持通過插件方式和諸如Spring等框架進行整合。

  • 更優(yōu)化的項目結(jié)構(gòu),不需要按照傳統(tǒng)的Java Web項目結(jié)構(gòu)那樣來分離代碼和WEB-INF,視圖可以和代碼在一起,閱讀代碼更便利。

  • 攔截器和框架本身更緊密,提供Action、Controller和Global三個級別的"攔截器"(或者說過濾器)。

  • 豐富的Action的返回值,返回的可以是視圖、可以是重定向、可以是文件、可以是字符串、可以是Json數(shù)據(jù),可以是Javascript代碼等等。

  • 支持針對測試環(huán)境自動生成測試的視圖模型數(shù)據(jù),以便前端和后端可以同時開發(fā)項目。

  • 支持在開發(fā)的時候自動生成路由信息、模型綁定、異常處理等配置的信息頁面和調(diào)試頁面,方便開發(fā)和調(diào)試。

  • 提供一套通用的控件模版,使得,并且支持多種模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。

    嗯,看上去挺誘人的,這是一個不錯的開端,如果你要寫的框架自己都不覺得想用的話,那么別人就更不會有興趣來嘗試使用你的框架了。


    解決難點

    之 所以把解決難點放在開搞之前是因為,如果實現(xiàn)這個框架的某些特性,甚至說實現(xiàn)這個框架的主流程有一些核心問題難以解決,那么就要考慮對框架的特性進行調(diào) 整,甚至取消框架的開發(fā)計劃了。有的時候我們在用A平臺的時候發(fā)現(xiàn)一個很好用的框架,希望把這個框架移植到B平臺,這個想法是好的,但之所以在這以前這么 多年沒有人這么干過是因為這個平臺的限制壓根不可能實現(xiàn)這樣的東西。比如我們要實現(xiàn)一個MVC框架,勢必需要依賴平臺提供的反射特性,如果你的語言平臺壓 根就沒有運行時反射這個功能,那么這就是一個非常難以解決的難點。又比如我們在某個平臺實現(xiàn)一個類似于.NET平臺Linq2Sql的數(shù)據(jù)訪問框架,但如 果這個目標平臺的開發(fā)語言并不像C#那樣提供了類型推斷、匿名類型、Lambda表達式、擴展方法的話那么由于語法的限制你寫出來的框架在使用的時候是無 法像.NET平臺Linq2Sql那樣優(yōu)雅的,這就違背了實現(xiàn)框架的主要目的,實現(xiàn)新的框架也就變得意義不大了。

    對于我們要實現(xiàn)的MVC框 架貌似不存在什么根本性的無法解決的問題,畢竟在Java平臺已經(jīng)有很多可以參考的例子了。如果框架的實現(xiàn)總體上沒什么問題的話,就需要逐一評估框架的這 些新特性是否可以解決。建議對于每一個難點特性做一個原型項目來證明可行,以免在框架實現(xiàn)到一半的時候發(fā)現(xiàn)有無法解決的問題就比較尷尬了。

    分析一下,貌似我們要實現(xiàn)的這8大特性只有第1點要研究一下,看看如何免配置通過讓代碼方式讓我們的Web MVC框架可以和Servlet進行整合,如果無法實現(xiàn)的話,我們可能就需要把第1點特性從零配置改為一分鐘快速配置了。


    開搞

    首先需要給自己框架取一個名字,取名要考慮到易讀、易寫、易記,也需要盡量避免和市面上其它產(chǎn)品的名字重復(fù),還有就是最好不要起一個侮辱其它同類框架的名字以免引起公憤。
    如果將來打算把項目搞大的話,可以提前注冊一下項目的相關(guān)域名,畢竟現(xiàn)在域名也便宜,避免到時候項目名和域名差距很大,或項目的.com或.org域名對應(yīng)了一個什么不太和諧的網(wǎng)站這就尷尬了。
    然后就是找一個地方來托管自己的代碼,如果一開始不希望公開代碼的話,最好除了本地源代碼倉庫還有一個異地的倉庫以免磁盤損壞導(dǎo)致抱憾終身,當(dāng)然如果不怕出丑的話也可以在起步的時候就使用Github等網(wǎng)站來托管自己的代碼。


    總體設(shè)計

    對 于總體設(shè)計我的建議是一開始不一定需要寫什么設(shè)計文檔畫什么類圖,因為可能一開始的時候無法形成這么具體的概念,我們可以直接從代碼開始做第一步??蚣艿?使用者一般而言還是開發(fā)人員,拋開框架的內(nèi)在的實現(xiàn)不說,框架的API設(shè)計的好壞取決于兩個方面。對于普通開發(fā)人員而言就是使用層面的API是否易于使 用,拿我們的MVC框架舉例來說:

    最基本的,搭建一個HelloWorld項目,聲明一個Controller和Action,配置一個路由規(guī)則讓Get方法的請求可以解析到這個Action,可以輸出HelloWorld文字,怎么實現(xiàn)?
    如果要實現(xiàn)從Cookie以及表單中獲取相關(guān)數(shù)據(jù)綁定到Action的參數(shù)里面,怎么實現(xiàn)?
    如果要配置一個Action在調(diào)用前需要判斷權(quán)限,在調(diào)用后需要記錄日志,怎么實現(xiàn)?

    我們這里說的API,它不一定全都是方法調(diào)用的API,廣義上來說我們認為框架提供的接入層的使用都可以認為是API,所以上面的一些功能都可以認為是MVC框架的API。

    框架除了提供基本的功能,還要提供一定程度的擴展功能,使得一些復(fù)雜的項目能夠在某些方面對框架進行增強以適應(yīng)各種需求,比如:

  • 我的Action是否可以返回圖片驗證碼?

  • 我的Action的參數(shù)綁定是否可以從Memcached中獲取數(shù)據(jù)?

  • 如果出現(xiàn)異常,能否在開發(fā)的時候顯示具體的錯誤信息,在正式環(huán)境顯示友好的錯誤頁面并且記錄錯誤信息到數(shù)據(jù)庫?

  • 一 般而言如果要實現(xiàn)這樣的功能就需要自己實現(xiàn)框架公開的一些類或接口,然后把自己的實現(xiàn)"注冊"到框架中,讓框架可以在某個時候去使用這些新的實現(xiàn)。這就需 要框架的設(shè)計者來考慮應(yīng)該以怎么樣的友好形式公開出去哪些內(nèi)容,使得以后的擴展實現(xiàn)在自由度以及最少實現(xiàn)上的平衡,同時要兼顧外來的實現(xiàn)不破壞框架已有的 結(jié)構(gòu)。

    要想清楚這些不是一件容易的事情,所以在框架的設(shè)計階段完全可以使用從上到下的方式進行設(shè)計。也就是不去考慮框架怎么實現(xiàn),而是以一 個使用者的身份來寫一個框架的示例網(wǎng)站,API怎么簡單怎么舒服就怎么設(shè)計,只從使用者的角度來考慮問題。對于相關(guān)用到的類,直接寫一個空的類(能用接口 的盡量用接口,你的目的只是通過編譯而不是能運行起來),讓程序可以通過編譯就可以了。你可以從框架的普通使用開始寫這樣一個示例網(wǎng)站,然后再寫各種擴展 應(yīng)用,在此期間你可能會用到框架內(nèi)部的20個類,這些類就是框架的接入類,在你的示例網(wǎng)站通過編譯的那剎那,其實你已經(jīng)實現(xiàn)了框架的接入層的設(shè)計。

    這里值得一說的是API的設(shè)計蘊含了非常多的學(xué)問以及經(jīng)驗,要在目標平臺設(shè)計一套合理易用的API首先需要對目標平臺足夠了解,每一個平臺都有一些約定俗成的規(guī)范,如果設(shè)計的API能符合這些規(guī)范那么開發(fā)人員會更容易接受這個框架,此外還有一些建議:

  • 之 所以我們把API的設(shè)計先行,而不是讓框架的設(shè)計先行是因為這樣我們更容易設(shè)計出好用的API,作為框架的實現(xiàn)者,我們往往會進行一些妥協(xié),我們可能會為 了在框架內(nèi)部DRY而設(shè)計出一套丑陋的API讓框架的使用者去做一些重復(fù)的工作;我們也可能會因為想讓框架變得更松耦合強迫框架的使用者去使用到框架的一 些內(nèi)部API去初始化框架的組件。如果框架不是易用的,那么框架的內(nèi)部設(shè)計的再合理又有什么意義?

  • 盡量少暴露一些框架內(nèi)部的類名吧,對 于框架的使用者來說,你的框架對他一點都不熟悉,如果要上手你的框架需要學(xué)習(xí)一到兩個類尚可接受,如果要使用到十幾個類會頭暈?zāi)X脹的,即使你的框架有非常 多的功能以及配置,可以考慮提供一個入口類,比如創(chuàng)建一個ConfigCenter類作為入口,讓使用者可以僅僅探索這個類便可對框架進行所有的配置。

  • 一 個好的框架是可以讓使用者少犯錯誤的,框架的設(shè)計者務(wù)必要考慮到,框架的使用者沒有這個業(yè)務(wù)來按照框架的最佳實踐來做,所以在設(shè)計API的時候,如果你希 望API的使用者一定要按照某個方式來做的話,可以考慮設(shè)置一個簡便的重載來加載默認的最合理的使用方式而不是要求使用者來為你的方法初始一些什么依賴, 同時也可以在API內(nèi)部做一些檢測,如果發(fā)現(xiàn)開發(fā)人員可能會犯錯進行一些提示或拋出異常。好的框架無需過多的文檔,它可以在開發(fā)人員用的時候告知它哪里錯 了,最佳實踐是什么,即便他們真的錯了也能以默認的更合理的方式來彌補這個錯誤。

  • 建議所有的API都有一套統(tǒng)一的規(guī)范,比如入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和 ZZZService。API往往需要進行迭代和改良的,在首個版本中把好名字用掉也不一定是一個好辦法,最好還是給自己的框架各種API的名字留一點余 地,這樣以后萬一需要升級換代不至于太牽強。

  • 下一步工作就是把項目中那些空的類按照功能進行劃分。目的很簡單,就是讓你的框架 的100個類或接口能夠按照功能進行拆分和歸類,這樣別人一打開你的框架就可以馬上知道你的框架分為哪幾個主要部分,而不是在100個類中暈眩;還有因為 一旦在你的框架有使用者后你再要為API相關(guān)的那些類調(diào)整包就比困難了,即使你在創(chuàng)建框架的時候覺得我的框架就那么十幾個類無需進行過多的分類,但是在將 來框架變大又發(fā)現(xiàn)當(dāng)初設(shè)計的不合理,無法進行結(jié)構(gòu)調(diào)整就會變得很痛苦。因此這個工作還是相當(dāng)重要的,對于大多數(shù)框架來說,可以有幾種切蛋糕的方式:

  • 分 層。我覺得框架和應(yīng)用程序一樣,也需要進行分層。傳統(tǒng)的應(yīng)用程序我們分為表現(xiàn)層、邏輯層和數(shù)據(jù)訪問層,類似的對于很多框架也可以進行橫向的層次劃分。要分 層的原因是我們的框架要處理的問題是基于多層抽象的,就像如果沒有OSI七層模型,要讓一個HTTP應(yīng)用去直接處理網(wǎng)絡(luò)信號是不合理的也是不利于重用的。 舉一個例子,如果我們要寫一個基于Socket的RPC的框架,我們需要處理方法的代理以及序列化,以及序列化數(shù)據(jù)的傳輸,這完全是兩個層面的問題,前者 偏向于應(yīng)用層,后者偏向于網(wǎng)絡(luò)層,我們完全有理由把我們的框架分為兩個層面的項目(至少是兩個包),rpc.core和rpc.socket,前者不關(guān)心 網(wǎng)絡(luò)實現(xiàn)來處理所有RPC的功能,后者不關(guān)心RPC來處理所有的Socket功能,在將來即使我們要淘汰我們的RPC的協(xié)議了,我們也可以重用 rpc.socket項目,因為它和RPC的實現(xiàn)沒有任何關(guān)系,它關(guān)注的只是socket層面的東西。

  • 橫切。剛才說的分層是橫向的分 割,橫切是縱向的分割(橫切是跨多個模塊的意思,不是橫向來切的意思)。其實橫切關(guān)注點就是諸如日志、配置、緩存、AOP、IOC等通用的功能,對于這部 分功能,我們不應(yīng)該把他們和真正的業(yè)務(wù)邏輯混淆在一起。對于應(yīng)用類項目是這樣,對于框架類項目也是這樣,如果某一部分的代碼量非常大,完全有理由為它分出 一個單獨的包。對于RPC項目,我們可能就會把客戶端和服務(wù)端通訊的消息放在common包內(nèi),把配置的處理單獨放在config包內(nèi)。

  • 功能。也就是要實現(xiàn)一個框架主要解決的問題點,比如對于上面提到的RPC框架的core部分,可以想到的是我們主要解決是客戶端如何找到服務(wù)端,如何把進 行方法調(diào)用以及把方法的調(diào)用信息傳給目標服務(wù)端,服務(wù)端如何接受到這樣的信息根據(jù)配置在本地實例化對象調(diào)用方法后把結(jié)果返回客戶端三大問題,那么我們可能 會把項目分為routing、client、server等幾個包。

  • 如果是一個RPC框架,大概是這樣的結(jié)構(gòu):

    對于我們的Web MVC框架,舉例如下:

  • 我們可以有一個mvc.core項目,細分如下的包:

  • common:公共的一組件,下面的各模塊都會用到

  • config:配置模塊,解決框架的配置問題

  • startup:啟動模塊,解決框架和Servlet如何進行整合的問題

  • plugin:插件模塊,插件機制的實現(xiàn),提供IPlugin的抽象實現(xiàn)

  • routing:路由模塊,解決請求路徑的解析問題,提供了IRoute的抽象實現(xiàn)和基本實現(xiàn)

  • controller:控制器模塊,解決的是如何產(chǎn)生控制器

  • model:視圖模型模塊,解決的是如何綁定方法的參數(shù)

  • action:action模塊,解決的是如何調(diào)用方法以及方法返回的結(jié)果,提供了IActionResult的抽象實現(xiàn)和基本實現(xiàn)

  • view:視圖模塊,解決的是各種視圖引擎和框架的適配

  • filter:過濾器模塊,解決是執(zhí)行Action,返回IActionResult前后的AOP功能,提供了IFilter的抽象實現(xiàn)以及基本實現(xiàn)

  • 我們可以再創(chuàng)建一個mvc.extension項目,細分如下的包:

  • filters:一些IFilter的實現(xiàn)

  • results:一些IActionResult的實現(xiàn)

  • routes:一些IRoute的實現(xiàn)

  • plugins:一些IPlugin的實現(xiàn)

  • 這里我們以IXXX來描述一個抽象,可以是接口也可以是抽象類,在具體實現(xiàn)的時候根據(jù)需求再來確定。

    這 種結(jié)構(gòu)的劃分方式完全吻合上面說的切蛋糕方式,可以看到除了橫切部分和分層部分,作為一個Web MVC框架,它核心的組件就是routing、model、view、controller、action(當(dāng)然,對于有些MVC框架它沒有route部 分,route部分是交由Web框架實現(xiàn)的)。

    如果我們在這個時候還無法確定框架的模塊劃分的話,問題也不大,我們可以在后續(xù)的搭建龍骨的步驟中隨著更多的類的建立,繼續(xù)理清和確定模塊的劃分。

    經(jīng)過了設(shè)計的步驟,我們應(yīng)該心里對下面的問題有一個初步的規(guī)劃了:

    • 我們的框架以什么形式來提供如何優(yōu)雅的API?

    • 我們的框架包含哪些模塊,模塊大概的作用是什么?


    搭建龍骨

    在 經(jīng)過了初步的設(shè)計之后,我們可以考慮為框架搭建一套龍骨,一套抽象的層次關(guān)系。也就是用抽象類、接口或空的類實現(xiàn)框架,可以通過編譯,讓框架撐起來,就像 造房子搭建房子的鋼筋混凝土結(jié)構(gòu)(添磚加瓦是后面的事情,我們先要有一個結(jié)構(gòu))。對于開發(fā)應(yīng)用程序來說,其實沒有什么撐起來一說,因為應(yīng)用程序中很多模塊 都是并行的,它可能并沒有一個主結(jié)構(gòu),主流程,而對于框架來說,它往往是一個高度面向?qū)ο蟮?#xff0c;高度抽象的一套程序,搭建龍骨也就是搭建一套抽象層。這么說 可能有點抽象,我們還是來想一下如果要做一個Web MVC框架,需要怎么為上面說的幾個核心模塊進行抽象(我們也來體會一下框架中一些類的命名,這里我們?yōu)榱烁逦?#xff0c;為所有接口都命名為IXXX,這點不太 符合Java的命名規(guī)范):

  • routing MVC的入口是路由

  • 每一個路由都是IRoute代表了不同的路由實現(xiàn),它也提供一個getRouteResult()方法來返回RouteResult對象

  • 我們實現(xiàn)一個框架自帶的DefaultRoute,使得路由支持配置,支持默認值,支持正則表達式,支持約束等等

  • 我們需要有一個Routes類來管理所有的路由IRoute,提供一個findRoute()方法來返回RouteResult對象,自然我們這邊調(diào)用的就是IRoute的getRouteResult()方法,返回能匹配到的結(jié)果

  • RouteResult對象就是匹配的路由信息,包含了路由解析后的所有數(shù)據(jù)

  • controller 路由下來是控制器

  • 我們有IControllerFactory來創(chuàng)建Controller,提供createController()方法來返回IController

  • IController代表控制器,提供一個execute()方法來執(zhí)行控制器

  • 我們實現(xiàn)一個框架自帶的DefaultControllerFactory來以約定由于配置的方式根據(jù)約定規(guī)則以及路由數(shù)據(jù)RouteResult來找到IController并創(chuàng)建它

  • 我 們?yōu)镮Controller提供一個抽象實現(xiàn),AbstractController,要求所有MVC框架的使用者創(chuàng)建的控制器需要繼承 AbstractController,在這個抽象實現(xiàn)中我們可以編寫一些便捷的API以便開發(fā)人員使用,比如view()方法、file()方法、 redirect()方法、json()方法、js()方法等等

  • action 找到了控制器后就是來找要執(zhí)行的方法了

  • 我們有IActionResult來代表Action返回的結(jié)果,提供一個execute()方法來執(zhí)行這個結(jié)果

  • 我們的框架需要實現(xiàn)一些自帶的IActionResult,比如ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對應(yīng)AbstractController的一些便捷方法

  • 再來定義一個IActionInvoker來執(zhí)行Action,提供一個invokeAction()方法

  • 我們需要實現(xiàn)一個DefaultActionInvoker以默認的方式進行方法的調(diào)用,也就是找到方法的一些IFilter按照一定的順序執(zhí)行他們,最后使用反射進行方法的調(diào)用得到上面說的IActionResult并執(zhí)行它的execute()方法

  • filter 我們的框架很重要的一點就是便捷的過濾器

  • 剛才提到了IFilter,代表的是一個過濾器,我們提供IActionFilter對方法的執(zhí)行前后進行過濾,提供IResultFilter對IActionResult執(zhí)行前后進行過濾

  • 我們的IActionInvoker怎么找到需要執(zhí)行的IFilter呢,我們需要定義一個IFilterProvider來提供過濾器,它提供一個getFilters()方法來提供所有的IFilter的實例

  • 我 們的框架可以實現(xiàn)一些自帶的IFilterProvider,比如AnnotationFilterProvider通過掃描Action或 Controller上的注解來獲取需要執(zhí)行的過濾器信息;比如我們還可以實現(xiàn)GlobalFilterProvider,開發(fā)人員可以直接通過配置或代 碼方式告知框架應(yīng)用于全局的IFilter

  • 既然我們實現(xiàn)了多個IFilterProvider,我們自然需要有一個類來管理這些IFilterProvider,我們實現(xiàn)一個FilterProviders類并提供getFilters()方法(這和我們的Routes類來管理IRoute是類似的,命名統(tǒng)一)

  • view 各種IActionResult中最特殊最復(fù)雜的就是ViewResult,我們需要有一個單獨的包來處理ViewResult的邏輯

  • 我們需要有IViewEngine來代表一個模版引擎,提供一個getViewEngineResult()方法返回ViewEngineResult

  • ViewEngineResult包含視圖引擎尋找視圖的結(jié)果信息,里面包含IView和尋找的一些路徑等

  • IView自然代表的是一個視圖,提供render()方法(或者為了統(tǒng)一也可以叫做execute)來渲染視圖

  • 我 們的框架可以實現(xiàn)常見的一些模版引擎,比如FreemarkerViewEngine、VelocityViewEngine 等,VelocityViewEngine返回的ViewEngineResult自然包含的是一個實現(xiàn)IView的VelocityView,不會返回 其它引擎的IView

  • 同樣的,我們是不是需要一個ViewEngines來管理所有的IViewEngine呢,同樣也是實現(xiàn)findViewEngine()方法

  • common 這里可以放一些項目中各個模塊都要用到的一些東西

  • 比 如各種context,context代表的是執(zhí)行某個任務(wù)需要的環(huán)境信息,這里我們可以定義HttpContext、 ControllerContext、ActionContext和ViewContext,后者繼承前者,隨著MVC處理流程的進行,View執(zhí)行時的 上下文相比Action執(zhí)行時的上下文信息肯定是多了視圖的信息,其它同理,之所以把這個信息放在common里面而不是放在各個模塊自己的包內(nèi)是因為這 樣更清晰,可以一目了然各種對象的執(zhí)行上下文有一個立體的概念

  • 比如各種helper或utility

  • 接下去就不再詳細闡述model、plugin等模塊的內(nèi)容了。

    看到這里,我們來總結(jié)一下,我們的MVC框架在組織結(jié)構(gòu)上有著高度的統(tǒng)一:

    • 如果xxx本身并無選擇策略,但xxx的創(chuàng)建過程也不是一個new這么簡單的,可以由xxxFactory類來提供一個xxx

    • 如果我們需要用到很多個yyy,那么我們會有各種yyyProvider(通過getyyy()方法)來提供這些yyy,并且我們需要有一個yyyProviders來管理這些yyyProvider

    • 如果zzz的選擇是有策略性的,會按照需要選擇zzz1或zzzN,那么我們可能會有一個zzzs來管理這些zzz并且(通過findzzz()方法)來提供合適的zzz

    同 時我們框架的相關(guān)類的命名也是非常統(tǒng)一的,可以一眼看出這是實現(xiàn)、還是抽象類還是接口;是提供程序,是執(zhí)行結(jié)果還是上下文。當(dāng)然,在將來的代碼實現(xiàn)過程中 很可能會把很多接口變?yōu)槌橄箢愄峁┮恍┠J的實現(xiàn),這并不會影響項目的主結(jié)構(gòu)。我們會在模式篇對框架常用的一些高層設(shè)計模式做更多的介紹。

    到了這里,我們的項目里已經(jīng)有幾十個空的(抽象)類、接口了,其中也定義了各種方法可以把各個模塊串起來(各種find()方法和execute()方法),可以說整個項目的龍骨已經(jīng)建立起來了,這種感覺很好,因為我們心里很有底,我們只需要在接下去的工作中做兩個事情:

    • 實現(xiàn)各種DefaultXXX來走通主流程

    • 實現(xiàn)各種IyyyProvider和Izzz接口來完善支線流程


    走通主線流程

    所謂走通主線流程,就是讓這個框架可以以一個HelloWorld形式跑起來,這就需要把幾個核心類的核心方法使用最簡單的方式進行實現(xiàn),還是拿我們的MVC框架來舉例子:

    • 從startup開始,可能需要實現(xiàn)ServletContextListener來動態(tài)注冊我們框架的入口Servlet,暫且起名為DispatcherServlet吧,在這個類中我們需要走一下主線流程

    • 調(diào)用Routes.findRoute()獲得IRoute

    • 調(diào)用IRoute.getRouteResult()來獲得RouteResult

    • 使用拿到的RouteResult作為參數(shù)調(diào)用DefaultControllerFactory.createController()獲得IController(其實也是AbstractController)

    • 調(diào)用IController.execute()

    • 在 config中創(chuàng)建一個IConfig作為一種配置方式,我們實現(xiàn)一個DefaultConfig,把各種默認實現(xiàn)注冊到框架中去,也就是 DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,然后把各種 IViewEngine加入ViewEngines

    • 然后需要完成相關(guān)默認類的實現(xiàn):

    • 實現(xiàn)Routes.findRoute()

    • 實現(xiàn)DefaultRoute.getRouteResult()

    • 實現(xiàn)DefaultControllerFactory.createController()

    • 實現(xiàn)AbstractController.execute()

    • 實現(xiàn)DefaultActionInvoker.invokeAction()

    • 實現(xiàn)ViewResult.execute()

    • 實現(xiàn)ViewEngines.findViewEngine()

    • 實現(xiàn)VelocityViewEngine.getViewEngineResult()

    • 實現(xiàn)VelocityView.render()

    在這一步,我們并不一定要去觸碰filter和model這部分的內(nèi)容,我們的主線流程只是解析路由,獲得控制器,執(zhí)行方法,找到視圖然后渲染視圖。過濾器和視圖模型的綁定屬于增強型的功能,屬于支線流程,不屬于主線流程。

    雖 然在這里我們說了一些MVC的實現(xiàn),但本文的目的不在于教你實現(xiàn)一個MVC框架,所以不用深究每一個類的實現(xiàn)細節(jié),這里想說的是,在前面的龍骨搭建完后, 你會發(fā)現(xiàn)按照這個龍骨為它加一點肉上去實現(xiàn)主要的流程是順理成章的事情,毫無痛苦。在整個實現(xiàn)的過程中,你可以不斷完善common下的一些 context,把方法的調(diào)用參數(shù)封裝到上下文對象中去,不但看起來清楚且符合開閉原則。到這里,我們應(yīng)該可以跑起來在設(shè)計階段做的那個示例網(wǎng)站的 HelloWorld功能了。

    在這里還想說一點,有些人在實現(xiàn)框架的時候并沒有搭建龍骨的一步驟,直接以非OOP的方式實現(xiàn)了主線流程,這種方式有以下幾個缺點:

    不容易做到SRP單一指責(zé)原則,你很容易把各種邏輯都集中寫在一起,比如大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個框架就肥瘦不勻,有些類特別龐大有些類特別小。
    不容易做到OCP開閉原則,擴展起來不方便需要修改老的代碼,我們期望的擴展是實現(xiàn)新的類然后讓框架感知,而不是直接修改框架的某些代碼來增強功能。
    很難實現(xiàn)DIP依賴倒置原則,即使你依賴的確實是IService但其實就沒意義,因為它只有一個實現(xiàn),只是把他當(dāng)作幫助類來用罷了。


    實現(xiàn)各種支線流程

    我們想一下,對于這個MVC框架有哪些沒有實現(xiàn)的支線流程?其實無需多思考,因為我們在搭建龍骨階段的設(shè)計已經(jīng)給了我們明確的方向了,我們只需要把除了主線之外的那些龍骨上也填充一些實體即可,比如:

  • 實現(xiàn)更多的IRoute,并注冊到Routes

  • 實現(xiàn)更多的IViewEngine,并注冊到ViewEngines

  • 實現(xiàn)必要的IFilterProvider以及FilterProviders,把IFilterProvider注冊到FilterProviders

  • 增強DefaultActionInvoker.invokeAction()方法,在合適的時候調(diào)用這些IFilter

  • 實現(xiàn)更多的IActionResult,并且為AbstractController實現(xiàn)更多的便捷方法來返回這些IActionResult

  • ……實現(xiàn)更多model模塊的內(nèi)容和plugin模塊的內(nèi)容

  • 實現(xiàn)了這一步后,你會發(fā)現(xiàn)整個框架飽滿起來了,每一個包中不再是僅有的那些接口和默認實現(xiàn),而且會有一種OOP的爽快感,爽快感來源于幾個方面:

  • 面對接口編程抽象和多態(tài)的放心安心的爽快感

  • 為抽象類實現(xiàn)具體類享受到父類大量實現(xiàn)的滿足的爽快感

  • 實現(xiàn)了大量的接口和抽象類后充實的爽快感

  • 我們再來總結(jié)一下之前說的那些內(nèi)容,實現(xiàn)一個框架的第一大步就是:

  • 設(shè)計一套合理的接口

  • 為框架進行模塊劃分

  • 為框架搭建由抽象結(jié)構(gòu)構(gòu)成的骨架

  • 在這個骨架的基礎(chǔ)上實現(xiàn)一個HelloWorld程序

  • 為這個骨架的其它部分填充更多實現(xiàn)

  • 經(jīng) 過這樣的一些步驟后可以發(fā)現(xiàn)這個框架是很穩(wěn)固的,很平衡的,很易于擴展的。其實到這里很多人覺得框架已經(jīng)完成了,有血有肉,其實個人覺得只能說開發(fā)工作實 現(xiàn)了差不多30%,后文會繼續(xù)說,畢竟直接把這樣一個血肉之軀拿出去對外有點嚇人,我們需要為它進行很多包裝和完善。

    單元測試

    在這之前我們寫的框架只能說是一個在最基本的情況下可以使用的框架,作為一個框架我們無法預(yù)測開發(fā)人員將來會怎么使用它,所以我們需要做大量的工作來確??蚣懿坏鞣N功能都是正確的,而且還是健壯的。寫應(yīng)用系統(tǒng)的代碼,大多數(shù)項目是不會去寫單元測試的,原因很多:

    • 項目趕時間,連做一些輸入驗證都沒時間搞,哪里有時間寫測試代碼。

    • 項目對各項功能的質(zhì)量要求不高,只要能在標準的操作流程下功能可用即可。

    • 項目基本不會去改或是臨時項目,一旦測試通過之后就始終是這樣子了,沒有迭代。

    • ……

    對于框架,恰恰相反,沒有配套的單元測試的框架(也就是僅僅使用人工的方式進行測試,比如在main中調(diào)用一些方法觀察日志或輸出,或者運行一下示例項目查看各種功能是否正常,是非常可怕的)原因如下:

  • 自動化程度高,回歸需要的時間短,甚至可以整合到構(gòu)建過程中進行,這是人工測試無法實現(xiàn)的。

  • 框架一定是有非常多的迭代和重構(gòu)的, 每一次修改雖然只改了A功能,但是可能會影響到B和C功能,人工測試的話你可能只會驗證A是否正常,容易忽略B和C,使用單元測試的話只要所有功能都有覆蓋,那么幾乎不可能遺漏因為修改導(dǎo)致的潛在問題,而且還能反饋出來因為修改導(dǎo)致的兼容性問題。

  • 之前說過,一旦框架開放出去,框架的使用者可能會以各種方式在各種環(huán)境來使用你的框架,環(huán)境不同會造成很多怪異的邊界輸入或非法輸入,需要使用單元測試對代碼進行嚴格的邊界測試,以確保框架可以在嚴酷的環(huán)境下生存。

  • 單元測試還能幫助我們改善設(shè)計,在寫單元測試的時候如果發(fā)現(xiàn)目標代碼非常難以進行模擬難以構(gòu)建有效的單元測試,那么說明目標代碼可能有強依賴或職責(zé)過于復(fù)雜,一個被單元測試高度覆蓋的框架往往是設(shè)計精良的,符合高內(nèi)聚低耦合的框架。

  • 如果框架的時間需求不是特別緊的話,單元測試的引入可以是走通主線流程的階段就引入,越早引入框架的成熟度可能就會越高,以后重構(gòu)返工的機會會越小,框架的可靠性也肯定會大幅提高。之前我有寫過一個類庫項目,并沒有寫單元測試,在項目中使用了這個類庫一段時間也沒有出現(xiàn)任何問題,后來花了一點時間為類庫寫了單元測試,出乎我意料之外的是,我的類庫提供的所有API中有超過一半是無法通過單元測試的(原以為這是一個成熟的類庫,其實包含了數(shù)十個BUG),甚至其中有一個API是在我的項目中使用的。你可能會問,為什么在使用這個API的時候沒有發(fā)生問題而在單元測試的時候發(fā)生問題了呢?原因之前提到過,我是框架的設(shè)計者,我在使用類庫提供的API的時候是知道使用的最佳實踐的,因此我在使用的時候為類庫進行了一個特別的設(shè)置,這個問題如果不是通過單元測試暴露的話,那么其它人在使用這個類庫的時候基本都會遇到一個潛在的BUG。

    示范項目

    寫一個示例項目不僅僅是為了給別人參考,而且還能夠幫助自己去完善框架,對于示例項目,最好兼顧下面幾點:

  • 是一個具有一定意義的網(wǎng)站或系統(tǒng),而不是純粹為了演示特性而演示。這是因為,很多時候只有那些真正的業(yè)務(wù)邏輯才會暴露出問題,演示特性的時候我們總是有一些定勢思維會規(guī)避很多問題?;蛘呖梢蕴峁﹥蓚€項目,一個純粹演示特性,一個是示例項目。

  • 覆蓋盡可能多的特性或使用難點,在項目的代碼中提供一些注釋,很多開發(fā)人員不喜歡閱讀文檔,反而喜歡看一下示例項目直接上手(模仿示例項目,或直接拿示例項目中的代碼來修改)。

  • 項目中的代碼,特別是涉及到框架使用的代碼一定要規(guī)范,原因上面也說了,作為框架的設(shè)計者你不會希望大家復(fù)制的代碼粘帖的代碼一團糟吧。

  • 如果你的項目針對的不僅僅是Web項目,那么示例項目最好提供Web和桌面兩個版本,一來你自己容易發(fā)現(xiàn)因為環(huán)境不同帶來的使用差異,二來可以給予不同類型項目不同的最佳實踐。

  • 原文鏈接:http://www.cnblogs.com/lovecindywang/p/4444915.html

    點擊閱讀全文閱讀"程序員的春聯(lián),各種感悟各種嗨”

    有人用微信聊天,有人卻在微信中學(xué)習(xí),成長。下面是2016最HOT IT公眾號,趕快試試新的關(guān)注方法吧!關(guān)注方式 ★長按二維碼,選擇“識別圖中二維碼”進行關(guān)注。

    ?沒看夠?更多好文在閱讀原文

    總結(jié)

    以上是生活随笔為你收集整理的你知道如何写一个框架吗?详细步骤放送(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。