##項目簡介 應部門需求寫了Url批量替換工具,固然稍微改動便可作成其餘字符串的批量替換。功能並不複雜,有不少種實現方式(例如正則),值得記錄的是開發過程當中的設計和重構方式,能夠簡單借鑑或者之後項目的參考。html
##場景分析、設計 文件中出現url的狀況比較複雜,出現位置大體有如下幾種情景:正則表達式
http://price.bitauto.com
<a href="/user/detail.aspx?userid" target=_blank>
<img src="http://images.cnblogs.com/logo.gif"> 、<iframe src="ad.htm">
<asp:HyperLink id="" NavigateUrl="" runat="server">
Response.Redirect("error.aspx")
<add name="errorpage" value="404.aspx">
public const string HOME = "index.aspx";
、var actionPage = "save.aspx";
可是連接並非老是以直接的字符串形式出現,有可能以變量的形式出現,例如: <a href="<%# Eval(Container, "Url").ToString()%>" target=_blank>
,有時會將部分路徑進行組合獲得最終連接。例如:var searchpage = "search.aspx?keywords=" + keywords + "&searchtype=" + searchtype
所以工具在替換時進行了取捨。對情景1-5作了較好的判斷,對情景6,7沒有進行處理。 對情景1-5也只是替換了出現的字符串,對變量或者組合變量沒有進行考慮。這些例外狀況須要你們單獨進行處理 有一種值得說明的狀況是:相似於 img.bitauto.com這樣的域名並無進行處理。 由於其和對象引用的方式很是相似,例如: User.Role.Id 和 img.bitauto.com 經過正則很難區分。##第一次迭代-代碼開發 簡單的代碼重用:c#
此工具須要兩個基本功能:備份,小寫替換。所以開始時直接定義了兩個方法 Backup(string dir)和LowercaseLink(string dir),開發完成後,查看了一下代碼,發現一個問題:服務器
即文件夾遍歷,這兩個方法都對文件夾進行了一次遍歷,其代碼是重複的(.Net雖好,但並不萬能,例如文件夾的複製就沒有實現。須要本身來開發。)。所以我在想有什麼方法可以將遍歷的代碼只寫一遍,後來發現Backup,LowercaseLink這兩個方法參數和返回值相同,所以想到了委託,代碼以下:架構
<!-- lang: c# --> public delegate void HandleMethod(string filepath); public void Backup(string fileName)`{ //此處省略若干字 } public void LowercaseLink(string filepath){ //此處省略若干字 } public void Traversal(string dirName,HandleMethod method){ string[] subDirs = Directory.GetDirectories(dirName); foreach (string subDir in subDirs){ Traversal(subDir, method) } string[] files = Directory.GetFiles(dirName); foreach (string file in files){ method.Invoke(file); } }
調用以下: Traversal(selectedFolder, Backup);
Traversal(selectedFolder, LowercaseLink);
Traversal的第二個參數爲委託類型(至關於函數指針),調用時須要將實際的方法名傳遞過去,而後經過Invoke來觸發。函數
##第二次迭代-業務處理和表現層分離 在初版完成後,基本功能已經實現,能實現將文本中的全部連接的批量替換。此時全部的代碼都放在了Form1.cs文件中,只是經過不一樣的方法和屬性來區分。大體代碼以下:工具
<!-- lang: c# --> public bool IsBackup; public delegate void HandleMethod(string filepath); public List<string> linkPatterns = new List<string>(); public string selectedFolder = string.Empty; private void btnStart_Click(object sender, EventArgs e){} private void btnSelectFolder_Click(object sender, EventArgs e){} //方法代碼 public void Backup(string fileName){} public void Traversal(string dirName,HandleMethod method){} public void InitPattern(){} public void LowercaseLink(string filepath){}
在對代碼進行了一些考慮之後,發現下面的四個方法和上面的兩個方法是不一樣的。上面是界面代碼的事件響應(屬於表現層),下面的纔是真正的業務處理(屬於業務處理層)。後來又添加一次操做狀態和信息的響應,此時仍然堆積在Form1中,代碼體積開始變得龐大和混亂。 所以對代碼進行了分離。學習
分離後代碼以下:測試
<!-- lang: c# --> public class LowercaseManager{ public ProjectStatus DoTask(LowercaseManagerStartupInfo startInfo){} public bool CheckBackup(string folder){}; public void TraversalBackup(string dirName, LowercaseManagerStartupInfo startInfo){}; public void Traversal(string dirName, HandleMethod method){}; public void LowercaseLink(string filepath){} } public class LowercaseManagerStartupInfo{ public bool IsBackup; public string BackupFolder; public string ProjectFolder; } public enum ProjectStatus { NoProject, ProjectFolderNotExist, NeedBackupFolder, Success } public partial class Form1 : Form public const string NEED_BACKUPFLODER = "請選擇備份文件夾"; public const string MSG_TITLE = "提示"; public const string NEED_PROJECT = "請選擇要替換的項目文件夾"; public const string PROJECT_FOLDER_NOT_EXIST = "項目文件夾不存在"; public const string SUCCESS = "替換成功"; private void btnStart_Click(object sender, EventArgs e) private void btnSelectFolder_Click(object sender, EventArgs e) private void btnBackup_Click(object sender, EventArgs e) private void cbIsBackup_CheckedChanged(object sender, EventArgs e)
這還有一個意外,剛開始時,並無想到使用LowercaseManagerStartupInfo這個參數類,而是將參數放到了LowercaseManager裏面,後來想到調用線程類時Thread,Process等類都將入口參數變爲了類xxxxStartInfo,所以這裏簡單的模仿了一下,連名字都抄了過來((∩_∩))。 在面向過程的開發中,是以函數做爲開發單元;而在面向對象的開發中,是以類做爲開發單元的。ui
##第三次迭代-擴展與可配置化
截止此時,代碼已經開發完畢。下午再次瀏覽代碼時,忽然想到,若是要實現將匹配的字符串替換爲大寫或者替換爲其餘要求(首字母大寫,統一替換爲xxx等),這時應該怎麼作。 最開始的想法是直接在public void LowercaseLink(string filepath)方法中添加 switch來實現。代碼可能以下:
<!-- lang: c# --> 在此輸入代碼 foreach (string pattern in linkPatterns){ MatchCollection urls = Regex.Matches(fileContent, pattern); foreach (Match url in urls){ switch(replaceType){ case "lower": fileBuilder.Replace(url.Value, url.Value.ToLower()); break; case "upper": fileBuilder.Replace(url.Value, url.Value.ToUpper()); break; ... } } }
可是這樣的設計感受並非很好,同時不利於配置,擴展起來也不是特別嚴謹和清晰。忘了哪位大人物曾經說過:「當你看到switch分支過多的時候,能夠想一想策略,命令等模式」。 呵呵,策略模式來敲門了(那些大人物若是看到實現的並非策略模式的話,別敲我頭就行了)。 因而實現以下:
<!-- lang: c# --> 在此輸入代碼 public interface IReplace { string ToDest(string src); } public class ReplaceFactory { public static IReplace lower = new LowerReplace(); public static IReplace CreateInstance(){ //配置return (IReplace)Assembly.Load(path).CreateInstance(className); return lower; } } public class LowerReplace : IReplace { // } public class UpperReplace : IReplace { // }
注意紅色那行代碼,當咱們須要替換爲不一樣的目的字符時,那麼建立不一樣類的示例便可(path,className從配置文件加載)。若是沒有你要的類,則須要繼承 IReplace接口,創建本身的類。 這樣即可規範類,方便管理和擴展,同時實現了可配置化
此時就會遇到一個問題:原來咱們的類叫作:LowercaseManager和LowercaseManagerStartupInfo,只是爲替換小寫使用,如今可替換爲不一樣的目標字符串,因此須要來類名已經不太合適。此時對類名進行了更改:ReplaceManager和ReplaceManagerStartupInfo; 在配置方面新增長了一個類ConfigHelper.cs輔助讀取配置文件。
設想(如下出現的代碼暫時沒寫,只是預想) 至此,算是作了一些代碼重碼。在進行簡單的測試時,出現了一些問題:
<a href="<%# Eval(Container, "Url")%>" >
相似的並無考慮。<!-- lang: c# --> public List<string> exts; exts = new List<string>(); exts.Add(".cs"); exts.Add(".html"); … if (exts.Contains(Path.GetExtension(filepath).ToLower())) //對指定文件進行替換 { …}
可是若是作成一個簡單過濾器彷佛是更好的選擇,代碼可能以下:
<!-- lang: c# --> 在此輸入代碼 public interface IFilter{ string DoFilter(string input); } public class FileFilter : IFilter
針對問題2: 偷了個懶,直接將代碼放到了LowerReplace裏面,對包含eval,bind等關鍵字的狀況進行了判斷,包含此關鍵字的則不進行替換. 實際上不進行替換也是替換的一種,也能夠作成類,叫作NoReplace,實現可能以下:public class NoReplace : IReplace
這樣連同咱們已經實現的替換類,如今已經有不少了: public class NoReplace : IReplace
public class UpperReplace : IReplace
public class LowerReplace : IReplace
之後可能更多。這時就會出現一個問題,在使用ReplaceFactory.CreateInstance()進行Replace實例建立時,到底要建立哪一個類。配置文件只能解決單一類的狀況,可是如今要根據字符串包含的某些字符或者其餘特徵判斷後,動態的進行建立。
固然能夠在Factory類型裏面進行判斷,也能夠創建單獨類 public class ReplaceRouter / ReplaceMap 將其路由到正確的Replace類。
針對問題3: 這個很好解決,忽略大小寫便可。
一路走來,就會發現,其實類的設計與架構並非一開始就能肯定的,而是隨着問題的不斷深刻,設計的不斷迭代而逐步成型的。這須要時間,精力和經驗,更須要不斷的自我否認,對代碼藝術的不斷追求,不然所謂的重構,設計只能是空談。
固然,如今的代碼也未盡善盡美,沒有其餘辦法,只有繼續學習,不斷超越。
源碼下載 http://files.cnblogs.com/hellofox2000/Url-Lower%E6%BA%90%E7%A0%81.rar