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

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

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

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

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

      在上一篇我們以簡單明快的方式介紹了置換型模版引擎的關鍵技術——模板標記的流式解析。采用流式解析可以達到相當好的解析性能,因為它基本上只需要對字符串(模板)掃描一次就可以完成所有代碼的解析。不像String.Split()和正則表達式那樣會造成很多迭代效應。今天我們引入一個較為復雜的示例,然后封裝一個實用級別的模板引擎。封裝就意味著使用者無需了解內部如何實現,只需要知道如何引用即可(為了降低門檻,本文沒有進行高級封裝和重構,這些內容在下一篇文章中放出)。

      概述

      題外話:在某公司入職之后,我曾經非常抱怨其CRM系統代碼架構的糟糕程度,其中比較重要的一點是不倫不類的面向對象/過程的編碼以及各種無法重用或無意重用的代碼。一位同事便向我請教,如何編寫面向對象的應用程序呢?實際上面向對象首先是一種深度思維的結果,方法就只有一個:把一切都當作對象!

      回到我們今天的話題,想做好面向對象的設計,首先要明確一下我們要做什么——我們要做的是一個模板引擎。它應當能夠解析一些模板代碼,然后根據外部業務數據生成我們期望的結果。當不關心如何實現這些需求的時候,可以先定義一個接口(暫時不要關心這個接口定義是否合理,否則哪里來的重構?)

       1 /// <summary>
      2 /// 定義模板引擎的基本功能。
      3 /// </summary>
      4 public interface ITemplateEngine
      5 {
      6 /// <summary>
      7 /// 解析模板。
      8 /// </summary>
      9 /// <param name="templateString">包含模板內容的字符串。</param>
      10 void Parser(string templateString);
      11
      12 /// <summary>
      13 /// 設定變量標記的值。
      14 /// </summary>
      15 /// <param name="key">鍵名。</param>
      16 /// <param name="value">值。</param>
      17 void SetValue(string key, object value);
      18
      19 /// <summary>
      20 /// 處理模板并輸出結果。
      21 /// </summary>
      22 /// <returns>返回包含業務數據的字符串。</returns>
      23 string Process();
      24 }

      定義了模板引擎的基本功能,我們就試著實現一下。為了讓大家接觸到更多的流式解析技巧,本例對上一篇文章中的標記語法做了更改,使其更為復雜。如果您仔細觀察上面的接口定義,會發現SetValue()方法的value參數被定義為object。我們的目標是滿足如下需求:

       1 [TestFixture]
      2 public sealed class TemplateEngineUnitTests
      3 {
      4 private const string _templateString = "[<time>{CreationTime:yyyy年MM月dd日 HH:mm:ss}</time>] <a href=\"{url}\">{title}</a>";
      5 private const string _html = "[<time>2012年04月03日 16:30:24</time>] <a href=\"http://www.ymind.net/\">陳彥銘的博客</a>";
      6
      7 [Test]
      8 public void ProcessTest()
      9 {
      10 var templateEngine = new TemplateEngine();
      11 templateEngine.Parser(_templateString);
      12 templateEngine.SetValue("url", "http://www.ymind.net/");
      13 templateEngine.SetValue("title", "陳彥銘的博客");
      14 templateEngine.SetValue("CreationTime", new DateTime(2012, 4, 3, 16, 30, 24));
      15
      16 var html = templateEngine.Process();
      17
      18 Trace.WriteLine(html);
      19
      20 Assert.AreEqual(html, _html);
      21 }
      22 }

      有經驗的朋友可能已經發現了,這不是個單元測試么?是的,在這里老陳使用了測試驅動開發的思路(我會盡量的在我的博文中給大家分享各方面的經驗技巧,這才是傳說中的干貨?。?/span>。測試驅動開發有什么好處?很顯然,有了單元測試代碼,我們就很明確的知道我們要做什么了,而且單元測試本身就是一個demo。你還需要文檔嗎?文檔在很多時候并不是必要的,但在某些時候又是非要不可的,要區別對待。

      奔著這個單元測試代碼,我們基本可以明確今天的學習內容:

      1. 標記格式和上一課一樣,都是“{label}”
      2. 今天加強的是允許對某些變量自定義格式化字符串,這里以日期類型為舉例。聰明的你一定想到了,就是在輸出Token流的時候需要有一段類似于dateTime.ToString("yyyy年MM月dd日 HH:mm:ss") 的代碼。
      3. 由于增加了一個格式化參數的語法,在“{label}”內部又需要將format字符串分離出來,因此解析的難度加大。

      模板解析

      根據上一節課的內容,我們首先來分析一下解析過程中所需要使用的狀態:

       1 /// <summary>
      2 /// 表示詞法分析模式的枚舉值。
      3 /// </summary>
      4 /// <remarks>記得上次我們的命名是PaserMode么?今天我們換個更加專業的單詞。</remarks>
      5 public enum LexerMode
      6 {
      7 /// <summary>
      8 /// 未定義狀態。
      9 /// </summary>
      10 None = 0,
      11
      12 /// <summary>
      13 /// 進入標簽。
      14 /// </summary>
      15 EnterLabel,
      16
      17 /// <summary>
      18 /// 脫離標簽。
      19 /// </summary>
      20 LeaveLabel,
      21
      22 /// <summary>
      23 /// 進入格式化字符串。
      24 /// </summary>
      25 EnterFormatString,
      26
      27 /// <summary>
      28 /// 脫離格式化字符串。
      29 /// </summary>
      30 LeaveFormatString,
      31 }

      請注意,每個模式都是成對出現的,因為流式解析總會是有始有終的!哪怕某些開始和結束在物理上是重合的。但是Enter和Leave這兩個動作總是在描述同樣一件事物,我們就可以縮減對象類型(這里是指詞法分析模式),優化后定義如下:

       1 /// <summary>
      2 /// 表示詞法分析模式的枚舉值。
      3 /// </summary>
      4 /// <remarks>記得上次我們的命名是PaserMode么?今天我們換個更加專業的單詞。</remarks>
      5 public enum LexerMode
      6 {
      7 /// <summary>
      8 /// 未定義狀態。
      9 /// </summary>
      10 Text = 0,
      11
      12 /// <summary>
      13 /// 進入標簽。
      14 /// </summary>
      15 Label = 1,
      16
      17 /// <summary>
      18 /// 進入格式化字符串。
      19 /// </summary>
      20 FormatString = 2,
      21 }

      不過我們今天要強化的可不只是增加了一個格式化字符串這么簡單,我們還要能夠明確的了解到每個Token的位置信息和類型,這是我們下一節講解解釋型模版引擎時所需要用到的概念。Token在上一節中我們僅僅使用了一個string類型來表示,但這個滿足不了我們的需要了,我們需要自定義一個Token類型,如下:

       1 /// <summary>
      2 /// 表示一個 Token。
      3 /// </summary>
      4 public sealed class Token
      5 {
      6 /// <summary>
      7 /// 初始化 <see cref="Token"/> 對象。
      8 /// </summary>
      9 /// <param name="kind"><see cref="TokenKind"/> 的枚舉值之一。</param>
      10 /// <param name="text">Token 文本。</param>
      11 /// <param name="line">Token 所在的行。</param>
      12 /// <param name="column">Token 所在的列。</param>
      13 public Token(TokenKind kind, string text, int line, int column)
      14 {
      15 this.Text = text;
      16 this.Kind = kind;
      17 this.Column = column;
      18 this.Line = line;
      19 }
      20
      21 /// <summary>
      22 /// 獲取 Token 所在的列。
      23 /// </summary>
      24 public int Column { get; private set; }
      25
      26 /// <summary>
      27 /// 獲取 Token 所在的行。
      28 /// </summary>
      29 public int Line { get; private set; }
      30
      31 /// <summary>
      32 /// 獲取 Token 類型。
      33 /// </summary>
      34 public TokenKind Kind { get; private set; }
      35
      36 /// <summary>
      37 /// 獲取 Token 文本。
      38 /// </summary>
      39 public string Text { get; private set; }
      40 }

      我們使用行數、列數、類型和文本(內容)來共同描述一個Token,這下可豐富多彩了!TokenKind明顯應該是個枚舉值,根據本例,TokenKind的定義如下:

       1 /// <summary>
      2 /// 表示 Token 類型的枚舉值。
      3 /// </summary>
      4 public 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 /// 標簽。
      28 /// </summary>
      29 Label = 4,
      30
      31 /// <summary>
      32 /// 格式化字符串前導符號。
      33 /// </summary>
      34 FormatStringPreamble = 5,
      35
      36 /// <summary>
      37 /// 格式化字符串。
      38 /// </summary>
      39 FormatString = 6,
      40 }

      也就是說本次我們將要面對5種Token(None純粹是為了描述一個空類型)!

      在往下看之前請您按照上一課中的方法自行實現一下本節課的需求,1小時之后再回來。

      如果您自己推敲過了,可能會發現一個問題,即FormatString是嵌套在Label里面的,這個貌似很難區分??!是的,本節之所以設計了這么一個需求,就是有了這么一個嵌套Token的解析過程,掌握這個技巧是至關重要的!因此,我希望您不要偷懶,自行先摸索摸索,先不要看后面的答案……

      實際上,如果您曾經接觸過編譯原理的話,可能如上的難題根本就不是什么事,因為這是一個司空見慣的問題。這整個就是方法簽名即形式參數的實現,比如:

      • Do()
      • Do("x")
      • Do("x", "y")
      • Do("x", y, "z")

      很眼熟很常見不是?那么在解析這些代碼的時候,由于模式會嵌套,也就意味著模式會后進先出。后進先出?!你想到了什么? 對!就是它,不要懷疑!Stack!只不過在泛型稱霸天下的今天,我們當然要選用Stack<T>了!這里我就不再帖出自己的實現代碼了,因為太長了。

      變量賦值

      變量賦值很簡單,就是使用Dictionary<string, object>:

       1 private readonly Dictionary<string, object> _variables = new Dictionary<string, object>();
      2
      3 /// <summary>
      4 /// 設定變量標記的值。
      5 /// </summary>
      6 /// <param name="key">鍵名。</param>
      7 /// <param name="value">值。</param>
      8 public void SetValue(string key, object value)
      9 {
      10 // 就這么簡單
      11 this._variables[key] = value;
      12 }

      這一小節沒有任何難度,難道說簡單一點不好么?

      數據輸出

      在輸出業務數據的時候,唯一的難點就是如何實現自定義格式化字符串,廢話不多說,直接上代碼:

       1 /// <summary>
      2 /// 處理模板并輸出結果。
      3 /// </summary>
      4 /// <returns>返回包含業務數據的字符串。</returns>
      5 public string Process()
      6 {
      7 var result = new StringBuilder();
      8
      9 for (var index = 0; index < this._tokens.Count; index++)
      10 {
      11 var token = this._tokens[index];
      12
      13 switch (token.Kind)
      14 {
      15 case TokenKind.Label:
      16 string value;
      17
      18 // 具體的Token流是:
      19                 // Label = CreationTime
      20                 // FormatStringPreamble = :
      21                 // FormatString = yyyy年MM月dd日 HH:mm:ss
      22                 // 因此這里減去2個索引值檢查操作范圍
      23 if (index < this._tokens.Count - 2)
      24 {
      25 // 實現自定義格式化字符串
      26 var nextToken = this._tokens[index + 2];
      27
      28 if (nextToken.Kind == TokenKind.FormatString)
      29 {
      30 // 注意這里使用 IFormattable 來驗證目標類型是否實現了格式化功能
      31 var obj = this._variables[token.Text] as IFormattable;
      32
      33 value = obj == null ? this._variables[token.Text].ToString() : obj.ToString(nextToken.Text, null);
      34 }
      35 else value = this._variables[token.Text].ToString();
      36 }
      37 else value = this._variables[token.Text].ToString();
      38
      39 result.Append(value);
      40 break;
      41
      42 case TokenKind.Text:
      43 result.Append(token.Text);
      44 break;
      45 }
      46 }
      47
      48 return result.ToString();
      49 }

      總結及代碼下載

      與上一課相比,本課的內容跨度較大,但學習和理解的難度尚且不是很大。我們下一節課將會對本節代碼進行重構封裝,看看重構能給我們帶來什么驚喜!

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


      下集預報:本課的代碼為了讓新手容易理解所以沒有做高度封裝,下一篇博文將會對本次的代碼執行一次高度封裝,代碼理解的難度較大,將會獨立出一個詞法分析器類、模板實體類等,充分的面向對象設計。
       

       

      posted @ 2012-04-05 09:41  O.C  閱讀(4036)  評論(9)    收藏  舉報
      主站蜘蛛池模板: 精品午夜福利在线观看| 亚洲天堂成人黄色在线播放| 国产99视频精品免视看9| 亚洲精品欧美综合二区| 国产精品自拍一二三四区| 99久久国产成人免费网站| 勐海县| 亚洲天堂成人一区二区三区| 欧美丰满熟妇xxxx性| 精品无码中文视频在线观看| 九九热精品在线免费视频| 最近中文字幕日韩有码| 中文字幕热久久久久久久| 精品久久精品久久精品久久| 二区中文字幕在线观看| 汾西县| 在线日韩日本国产亚洲| 国产成人麻豆亚洲综合无码精品| 方山县| 亚洲精品麻豆一二三区| 久久毛片少妇高潮| 免费国产一区二区不卡| 曰韩高清砖码一二区视频| 国产精品亚洲精品日韩已满十八小 | 国产成人精品18| 中文字幕国产精品自拍| 蜜芽久久人人超碰爱香蕉| 青青青青久久精品国产| 嫩草院一区二区乱码| 亚洲精品日韩在线观看| 亚洲av激情一区二区| 久久久无码精品国产一区| 人成午夜免费大片| 国产边打电话边被躁视频| 搡bbbb搡bbb搡| 精选国产av精选一区二区三区| 国产成人无码AV片在线观看不卡| 亚洲中文字幕无码不卡电影| 国产草草影院ccyycom| 无遮高潮国产免费观看| 少妇办公室好紧好爽再浪一点|