##项目简介 应部门需求写了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