<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      由淺入深:自己動手開發(fā)模板引擎——置換型模板引擎(四)

      受到群里兄弟們的竭力邀請,老陳終于決定來分享一下.NET下的模板引擎開發(fā)技術(shù)。本系列文章將會帶您由淺入深的全面認識模板引擎的概念、設(shè)計、分析和實戰(zhàn)應(yīng)用,一步一步的帶您開發(fā)出完全屬于自己的模板引擎。關(guān)于模板引擎的概念,我去年在百度百科上錄入了自己的解釋(請參考:模板引擎)。老陳曾經(jīng)自己開發(fā)了一套網(wǎng)鳥Asp.Net模板引擎,雖然我自己并不樂意去推廣它,但這已經(jīng)無法阻擋群友的喜愛了!

      概述

      置換型模板引擎系列是我們進入模板引擎開發(fā)領(lǐng)域的基礎(chǔ)課程,這里講述的一些原理、概念和實踐方案都是后續(xù)模板引擎開發(fā)中所需要用到的,正所謂是由淺入深、循序漸進!在編寫這些博文的時候,我遇到了很多阻力。為了能夠讓菜鳥朋友入門又不讓高手們嗤之以鼻感覺到木有干貨,這讓老陳真的是煞費苦心!如果僅僅是開源一份代碼出去,那么完成這樣的項目本身可能不需要多少時間,然而要把這些組織成文字分享給大家,實在是很頭疼的一件事情。

      最初,我只是想將整個置換型模板引擎分為兩節(jié)完成,但是發(fā)現(xiàn)不太可能,因此就不斷的拆分。第一課我們簡單了解了一些概念和原理,第二節(jié)我們深入探討了字符流解析為Token流的過程,而第三節(jié)我們將這種過程簡單的封裝了一些,并融入測試驅(qū)動開發(fā)的概念進去,借此給大家分享更多的開發(fā)技巧。而本節(jié),也是作為置換型模板引擎的最后一節(jié),將會對第三節(jié)課中我們所做的簡單封裝執(zhí)行重構(gòu)。

      我們今天重構(gòu)的理念就是使用面向?qū)ο笤O(shè)計的理念來歸納整理模板引擎的業(yè)務(wù)流程、分析實體并創(chuàng)建代碼模型以及建立單元測試等。我個人不是專業(yè)的寫手,每篇博文的本意都是為大家分享一些開發(fā)經(jīng)驗和技巧,但我不保證我的詞匯描述以及實踐方案的絕對準(zhǔn)確性。

      需求分析

      有了前面幾節(jié)課,我們對模板引擎的原理已經(jīng)有了非常清楚的認識,它本身的實現(xiàn)就是某種替換機制。為了追求高效、嚴謹,最后我們提到了按流替代式模板引擎并作出深入探討。經(jīng)歷了三節(jié)課的認知和學(xué)習(xí),我們知道按流替代式模板引擎的工作過程會經(jīng)歷如下階段:

      1. 解析模板:
        1. 以字符為單位解析模板代碼,并將代碼整理為Token流。在沒有復(fù)雜需求的前提下,每一個Token都是有著直接意義的。要么它表示普通的Text對象,會原原本本的輸出;要么 表示一種Label對象,在輸出的時候會被替換為真實的業(yè)務(wù)數(shù)據(jù);
        2. 有了Token流,按照順序就可以將Text對象和Label對象按照實際的業(yè)務(wù)需求進行輸出。實際上我們之前的舉例并沒有真正的深入到流的概念,使用的都是集合。集合與流的最大區(qū)別就是流只能向前,其中的每個元素基本上就只有一次訪問機會,而集合是任意的。
      2. 設(shè)定業(yè)務(wù)數(shù)據(jù);
      3. 處置并得到輸出結(jié)果。輸出結(jié)果可以保存到臨時變量,也可以直接輸出展示,此后變脫離模板引擎的業(yè)務(wù)范圍了。

      在第三節(jié)課中,我們引入了一個Label中的Label的概念,即上篇文章中的“{CreationTime:yyyy年MM月dd日 HH:mm:ss}”標(biāo)簽。 這個標(biāo)記使得我們不是死板的去替換Label,而是可以在模板中直接指定某些數(shù)據(jù)的輸出格式。那么把這種標(biāo)簽還理解為Label的話是不是不太合適了呢?如果未來我們增加更加復(fù)雜的語法呢?

      是的,為了使得流程更加清晰,我們再引入一個概念——Element。對!元素!就是模板元素!現(xiàn)在我們的解析流程變更為:

      1. 將字符流轉(zhuǎn)換為Token流;
      2. 將Token流轉(zhuǎn)換為Element流;
      3. 如果有可能,還需要把Element整理為Tag語句(這是解釋型引擎內(nèi)必備的東西)

      在這里留下一個作業(yè):請您結(jié)合這幾節(jié)講述的內(nèi)容整理出一個完整的模板引擎工作流程圖。

      實體建模

      在面向?qū)ο蟪绦蛟O(shè)計里,幾乎每一件事物都可以使用結(jié)構(gòu)等來描述,因為編程語言里面之所以支持命名空間、類、結(jié)構(gòu)、接口等概念,就是為了描述面向?qū)ο缶幊獭=裉煳以囍鴱囊粋€菜鳥的角度來分析和考慮如何實現(xiàn)實體建模,思路可能不太符合您的習(xí)慣,但我相信這樣的過程菜鳥們一定會喜歡!

      整個模板引擎分為兩個體系,一個是對外公開的業(yè)務(wù)引擎和實體,一個是對內(nèi)的代碼解析器和實體

      模板引擎的定義

      模板引擎自身不是現(xiàn)實中的一種實體,它是一種業(yè)務(wù),也可以理解為幫助類——即某種封裝。以下是思路:

      1. 模板引擎就是用來處置模板的,因此它需要有個模板的屬性,而這個模板是在模板引擎初始化時就存在的,模板引擎無權(quán)修改它;
      2. 處置模板本身就是做事的過程,這個需要定義為方法,通過這個方法我們應(yīng)該能捕獲處置結(jié)果;
      3. 要處置模板標(biāo)簽,需要一個預(yù)定義變量的容器,要提供一套添加變量、刪除變量等的方法;

      整理后我們使用接口描述,如下:

       1 /// <summary>
      2 /// 定義模板引擎的基本功能。
      3 /// </summary>
      4 public interface ITemplateEngine
      5 {
      6 /// <summary>
      7 /// 獲取模板。
      8 /// </summary>
      9 Template Template { get; }
      10
      11 /// <summary>
      12 /// 設(shè)定變量標(biāo)記的置換值。
      13 /// </summary>
      14 /// <param name="key">鍵名。</param>
      15 /// <param name="value">值。</param>
      16 void SetVariable(string key, object value);
      17
      18 /// <summary>
      19 /// 刪除變量標(biāo)記的置換值。
      20 /// </summary>
      21 /// <param name="key">鍵名。</param>
      22 void RemoveVariable(string key);
      23
      24 /// <summary>
      25 /// 清空變量標(biāo)記的置換值。
      26 /// </summary>
      27 void ClearVariables();
      28
      29 /// <summary>
      30 /// 處理模板。將處理結(jié)果保存到字符編寫器中。
      31 /// </summary>
      32 /// <param name="writer">指定一個字符編寫器。</param>
      33 void Process(TextWriter writer);
      34
      35 /// <summary>
      36 /// 處理模板。并將結(jié)果作為字符串返回。
      37 /// </summary>
      38 /// <returns>返回 <see cref="System.String"/></returns>
      39 string Process();
      40 }

      模板的定義

      在上文中我們提到,今天增加了一個Element的概念,那么模板的直接構(gòu)成者就是Element,就如HTML代碼是由各種Element和Text組成的一樣,Text是一種特殊的Element。那么,模板的描述就非常簡單了,它就是Element的集合:

       1 /// <summary>
      2 /// 定義一個模板。
      3 /// </summary>
      4 public interface ITemplate
      5 {
      6 /// <summary>
      7 /// 獲取模板的標(biāo)簽庫。
      8 /// </summary>
      9 List<Element> Elements { get; }
      10 }

      Element的定義

      Element是構(gòu)成模板的基本單位,然而Element并不是只有一種,前面我們提到最起碼會分為Label和Text兩種。既然是面向?qū)ο蟮脑O(shè)計,我們就使用多態(tài)性來描述Element。多態(tài)是指同一(種)事物的多種形態(tài),而不是指狀態(tài)。先來看看我們的模板代碼:

      [<time>{CreationTime:yyyy年MM月dd日 HH:mm:ss}</time>]\r\n<a href=\"{url}\">{title}</a>
      

      歸納一下:

      • 非{xxx}格式的都理解為普通Text,會原原本本的輸出;
      • {xxx}是Label
      • {xxx:xxx}是帶有格式化字符串的Label

      OK,那么就可以形成如下關(guān)系圖:

      圖中的VariableLabel和TextElement共同派生自Element,體現(xiàn)出了Element的多態(tài)性。FormattableVariableLabel派生自VariableLabel又提現(xiàn)了VariableLabel的多態(tài)性。這里,我們將Element定義為抽象類,就不需要定義接口了。如果要定義,那么這個接口就只需要兩個屬性:Line和Column。因為Element的共同特點就是有特定的位置,至于是否有數(shù)據(jù)在里面這個是說不定的事情!

      仔細觀察VariableLabel還獨自聲明了一個Process(Dictionary<string, object> variables)方法,這個將數(shù)據(jù)置換的過程移動到了Element自身。降低了整個代碼架構(gòu)的耦合性。

      另外,我們這里的Element定義實際上還缺少了對“{”、“}”和“:”等特殊字符的描述,他們也是模板代碼的基本元素之一。只不過,在解析過程中我們要忽略它們,這里即使定義了,也可能用不到。

      代碼解析器的定義

      代碼解析器就只有一個作用——將Token流轉(zhuǎn)換為Element集合,它應(yīng)該從詞法分析器初始化,也僅需要一個公開方法:

       1 /// <summary>
      2 /// 定義模板代碼解析器。
      3 /// </summary>
      4 internal interface ITemplateParser
      5 {
      6 /// <summary>
      7 /// 解析模板代碼。
      8 /// </summary>
      9 /// <returns>返回 <see cref="Element"/> 對象的集合。</returns>
      10 List<Element> Parse();
      11 }

      詞法分析器的定義

      詞法分析器的作用是將字符流轉(zhuǎn)換為Token流:

       1 /// <summary>
      2 /// 定義模板詞法分析器。
      3 /// </summary>
      4 internal interface ITemplateLexer
      5 {
      6 /// <summary>
      7 /// 繼續(xù)分析下一條詞匯,并返回分析結(jié)果。
      8 /// </summary>
      9 /// <returns>Token</returns>
      10 Token Next();
      11 }

      這里我們僅僅使用了一個唯一的Next()方法,它的返回值是Token。也就是說,詞法分析是一個只能向前的過程,現(xiàn)在您是否能夠領(lǐng)略到為什么我一直在強調(diào)Token流的概念么?作業(yè):請認真思考流和集合的區(qū)別。

      Token的定義

      實際上,Token與Element一樣,都有位置屬性。然而為了便于后期處理,我們還需要保存Token代表的數(shù)據(jù)(這里的Text,實際上應(yīng)該定義為Data更加合適,為了直觀,這里就Text吧!),還要指明當(dāng)前Token的類型(TokenKind):

       1 /// <summary>
      2 /// 定義一個 Token。
      3 /// </summary>
      4 internal interface IToken
      5 {
      6 /// <summary>
      7 /// 獲取 Token 所在的列。
      8 /// </summary>
      9 int Column { get; }
      10
      11 /// <summary>
      12 /// 獲取 Token 所在的行。
      13 /// </summary>
      14 int Line { get; }
      15
      16 /// <summary>
      17 /// 獲取 Token 類型。
      18 /// </summary>
      19 TokenKind Kind { get; }
      20
      21 /// <summary>
      22 /// 獲取 Token 文本。
      23 /// </summary>
      24 string Text { get; }
      25 }

      其他定義

      Token需要TokenKind來描述其類型,這是一個有限的狀態(tài)集合,那么就定義為枚舉值。詞法分析器這個東東,實際上在第二課第三課已經(jīng)見識過了,我們不斷的在不同的狀態(tài)中穿梭,那么就需要一個詞法分析狀態(tài)的枚舉值,這兩個枚舉值的定義分別如下:

       1 /// <summary>
      2 /// 表示 Token 類型的枚舉值。
      3 /// </summary>
      4 internal enum TokenKind
      5 {
      6 /// <summary>
      7 /// 未指定類型。
      8 /// </summary>
      9 None = 0,
      10
      11 /// <summary>
      12 /// 左大括號。
      13 /// </summary>
      14 LeftBracket = 1,
      15
      16 /// <summary>
      17 /// 右大括號。
      18 /// </summary>
      19 RightBracket = 2,
      20
      21 /// <summary>
      22 /// 普通文本。
      23 /// </summary>
      24 Text = 3,
      25
      26 /// <summary>
      27 /// 標(biāo)簽。
      28 /// </summary>
      29 Label = 4,
      30
      31 /// <summary>
      32 /// 格式化字符串前導(dǎo)符號。
      33 /// </summary>
      34 FormatStringPreamble = 5,
      35
      36 /// <summary>
      37 /// 格式化字符串。
      38 /// </summary>
      39 FormatString = 6,
      40
      41 /// <summary>
      42 /// 表示字符流末尾。
      43 /// </summary>
      44 EOF = 7
      45 }
      46
      47 /// <summary>
      48 /// 表示詞法分析模式的枚舉值。
      49 /// </summary>
      50 /// <remarks>記得上次我們的命名是PaserMode么?今天我們換個更加專業(yè)的單詞。</remarks>
      51 internal enum LexerMode
      52 {
      53 /// <summary>
      54 /// 未定義狀態(tài)。
      55 /// </summary>
      56 Text = 0,
      57
      58 /// <summary>
      59 /// 進入標(biāo)簽。
      60 /// </summary>
      61 Label = 1,
      62
      63 /// <summary>
      64 /// 進入格式化字符串。
      65 /// </summary>
      66 FormatString = 2,
      67 }

      單元測試

      完成了基本的實體接口定義,我們不著急編寫功能實現(xiàn)的代碼,而是先創(chuàng)建個單元測試,實現(xiàn)以測試為目的來驅(qū)動我們的開發(fā)過程。測試驅(qū)動開發(fā)的好處,是我們在開發(fā)之前就已經(jīng)知道了我們的編碼目標(biāo)!而平常我們經(jīng)常是需求驅(qū)動開發(fā)的,這個不算科學(xué),當(dāng)遇到多個團隊配合的時候,就顯得難以交流。較好的方案是:需求驅(qū)動測試、測試驅(qū)動開發(fā)、開發(fā)驅(qū)動猴子

      實際上,我們的單元測試代碼在上一課中就編寫過了,稍加修改如下:

       1 [TestFixture]
      2 public sealed class TemplateEngineUnitTests
      3 {
      4 private const string _templateString = "[<time>{CreationTime:yyyy年MM月dd日 HH:mm:ss}</time>]\r\n<a href=\"{url}\">{title}</a>";
      5 private const string _html = "[<time>2012年04月03日 16:30:24</time>]\r\n<a href=\"http://www.ymind.net/\">陳彥銘的博客</a>";
      6
      7 [Test]
      8 public void ProcessTest()
      9 {
      10 var templateEngine = TemplateEngine.FromString(_templateString);
      11
      12 templateEngine.SetVariable("url", "http://www.ymind.net/");
      13 templateEngine.SetVariable("title", "陳彥銘的博客");
      14 templateEngine.SetVariable("CreationTime", new DateTime(2012, 4, 3, 16, 30, 24));
      15
      16 var html = templateEngine.Process();
      17
      18 Trace.WriteLine(html);
      19
      20 // 還記得第一節(jié)課我就說,我為了簡化代碼架構(gòu)使用了單元測試的方法來做的demo代碼,那個不是真正的單元測試
      21 // 因為在那個時候,我們的代碼中沒有包含結(jié)果驗證的過程
      22
      23 // 對輸出結(jié)果進行測試驗證,首先不能是null
      24 Assert.NotNull(html);
      25
      26 // 輸出結(jié)果必須與預(yù)期結(jié)果完全一致
      27 Assert.AreEqual(_html, html);
      28
      29 // 如果以上兩個驗證無法通過,那么執(zhí)行的時候必定會報錯!
      30 }
      31 }

      做單元測試的方法有很多,我自己喜歡使用NUnit.Framework + ReSharper,效果如下圖:

      編碼實現(xiàn)

      編碼實現(xiàn)這一步主要講一下幾個難點,剩下的請仔細琢磨代碼。

      難點一:如何實現(xiàn)FormattableVariableLabel的Process()方法

      在.NET中,凡是支持自定義格式化字符串的對象必定都會實現(xiàn)IFormattable接口,利用這一點我們可以通過以下代碼實現(xiàn)這個需求,說難也不難:

       1 /// <summary>
      2 /// 處置當(dāng)前元素。
      3 /// </summary>
      4 /// <param name="variables">與當(dāng)前元素關(guān)聯(lián)的對象。</param>
      5 /// <returns>返回 <see cref="System.String"/></returns>
      6 public override string Process(Dictionary<string, object> variables)
      7 {
      8 if (variables == null || variables.Count == 0) return String.Empty;
      9 if (variables.ContainsKey(this.Name) == false) return String.Empty;
      10
      11 var obj = variables[this.Name] as IFormattable;
      12
      13 return obj == null ? variables[this.Name].ToString() : obj.ToString(this.Format, null);
      14 }

      難點二:如何將Token流轉(zhuǎn)換為Element集合

      我們?yōu)槊總€Token標(biāo)記了位置和類型信息,依照這些信息進行歸納整理即可。在處理的時候只需要理會Text和Label兩種類型即可,當(dāng)遇到Label類型時,還有可能要讀取FormatString,而在FormatString之前則必定是FormatStringPreamble!

      詳情請參考TemplateParser.Parse()方法的實現(xiàn)。

      難點三:詞法解析的過程只能向前會不會有問題?

      實際上,流是一種很普通的概念,水管里面的水只能是一個方向;電流只會從一端到另外一端;網(wǎng)絡(luò)數(shù)據(jù)流的發(fā)送和接受都是一次性的(如果您涉足過),如此等等。只能向前,這意味著更好的性能,因為這注定了某些事情我們只能做一次!

      詞法解析過程中可能要判斷前后依賴的字符和字符串(這里要理解為字符數(shù)組),這里就需要定位了,記得FileStream里面有個Position屬性么?呵呵,為什么我們就不能有呢?但是不要濫用它!

      限于篇幅,通過文字已經(jīng)無法準(zhǔn)確去描述這個過程了,希望大家能夠認真的研究TemplateLexer類!如果您搞不懂它,那么在制作解釋型模板引擎的時候?qū)龅胶艽蟮淖枇Γ〖佑停〔欢牡胤礁l(fā)問!!!

      總結(jié)及代碼下載

      置換型模板引擎系列分了4課才講述完畢,實際上還不夠完美,但時間倉促,也不想把篇幅拉的太長,希望大家能夠多多研究代碼。如果有問題請跟帖提出即可。

      本系列教程并沒有將話題集中在模板引擎自身,期間提到了狀態(tài)機、有限狀態(tài)機、編譯原理、詞法分析、單元測試、測試驅(qū)動開發(fā)、面向?qū)ο笤O(shè)計等等概念,希望大家能夠有所收獲!誠然,如果您發(fā)現(xiàn)了錯誤之處還請指出,歡迎挑刺!

      代碼下載:置換型模板引擎(4).zip


      4月9日之后我們將開始討論解釋型模板引擎,敬請關(guān)注!

      posted @ 2012-04-06 09:21  O.C  閱讀(3731)  評論(6)    收藏  舉報
      主站蜘蛛池模板: 亚洲人成自拍网站在线观看| 奇米四色7777中文字幕| 精品无码一区在线观看| 中文字幕无码av不卡一区| 国内精品久久人妻无码妲| 亚洲日本欧美日韩中文字幕| 久久精品蜜芽亚洲国产av| 婷婷六月天在线| 久久99久久99精品免视看国产成人| 亚洲国产精品无码一区二区三区| 色色97| 留坝县| 人人爽亚洲aⅴ人人爽av人人片| 精品中文人妻中文字幕| 精品无码av无码专区| 黄色大全免费看国产精品| 丰满少妇特黄一区二区三区| 乱人伦人妻系列| 色狠狠色噜噜AV一区| 深夜福利成人免费在线观看| 精品国产成人三级在线观看| 精品一卡2卡三卡4卡乱码精品视频| 黄色特级片一区二区三区| 51妺嘿嘿午夜福利| 亚洲av日韩av永久无码电影| 久青草国产在视频在线观看| 亚洲色欲在线播放一区| 亚洲人妻中文字幕一区| 麻豆一区二区三区精品蜜桃| 亚洲高清WWW色好看美女| 国产尤物精品自在拍视频首页| 国产一区二区三区亚洲精品| 欧美精品一区二区三区在线观看| 国产不卡免费一区二区| 国产精品免费中文字幕| 日本一道一区二区视频| 久久精品女人天堂av| 国产三级精品三级在线观看 | 十八禁国产一区二区三区| 亚洲国产韩国欧美在线| 国产丰满乱子伦无码专区|