批量替換工具的開發及迭代工程

##項目簡介 應部門需求寫了Url批量替換工具,固然稍微改動便可作成其餘字符串的批量替換。功能並不複雜,有不少種實現方式(例如正則),值得記錄的是開發過程當中的設計和重構方式,能夠簡單借鑑或者之後項目的參考。html

##場景分析、設計 文件中出現url的狀況比較複雜,出現位置大體有如下幾種情景:正則表達式

  1. 以http等開頭的連接 例如: http://price.bitauto.com
  2. 錨標籤 例如 <a href="/user/detail.aspx?userid" target=_blank>
  3. 文件源,以src爲表現 例如:<img src="http://images.cnblogs.com/logo.gif"> 、<iframe src="ad.htm">
  4. 服務器端連接 例如: <asp:HyperLink id="" NavigateUrl="" runat="server">
  5. 轉向 例如: Response.Redirect("error.aspx")
  6. 配置文件 例如: <add name="errorpage" value="404.aspx">
  7. 變量 例如: 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輔助讀取配置文件。

設想(如下出現的代碼暫時沒寫,只是預想) 至此,算是作了一些代碼重碼。在進行簡單的測試時,出現了一些問題:

  1. 有些文件不須要進行過濾,例如擴展名爲exe,dll等
  2. 相似<a href="<%# Eval(Container, "Url")%>" > 相似的並無考慮。
  3. 原來的正則表達式區分了大小寫。 所以對程序進行簡單的更改。 針對問題1 ,直接在代碼裏進行了擴展名的判斷。
<!-- 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

相關文章
相關標籤/搜索