文档章节

批量替换工具的开发及迭代工程

h
 hellofox2000
发布于 2015/03/10 12:47
字数 2323
阅读 41
收藏 0

##项目简介 应部门需求写了Url批量替换工具,当然稍微改动即可做成其他字符串的批量替换。功能并不复杂,有很多种实现方式(例如正则),值得记录的是开发过程中的设计和重构方式,可以简单借鉴或者以后项目的参考。

##场景分析、设计 文件中出现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 通过正则很难区分。

##第一次迭代-代码开发 简单的代码重用:

此工具需要两个基本功能:备份,小写替换。因此开始时直接定义了两个方法 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,因此这里简单的模仿了一下,连名字都抄了过来((∩_∩))。 在面向过程的开发中,是以函数作为开发单元;而在面向对象的开发中,是以类作为开发单元的。

##第三次迭代-扩展与可配置化

截止此时,代码已经开发完毕。下午再次浏览代码时,突然想到,如果要实现将匹配的字符串替换为大写或者替换为其他要求(首字母大写,统一替换为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

© 著作权归作者所有

共有 人打赏支持
h
粉丝 2
博文 5
码字总数 4611
作品 0
海淀
如何自动化重构前 ES6 老代码

在今天,JavaScript 的语言标准迭代非常迅速,这固然是好事,但也加速了许多前端老项目中代码的腐化。相信接手过有一段历史的老项目的同学对此都多少有所感触。那么我们是否能自动化、无痛地...

doodlewind
06/29
0
0
Android Ant 批量多渠道打包实例

Android Ant 批量多渠道打包实例 关于批量打包,无需多言,这是每个国内Android开发者必须面对的一个问题。 下面,我就以开源项目「知乎小报」为例,详细说明如何使用ANT实现批量打渠道包。 ...

Cundong
2014/10/17
0
7
Java Web 框架--jWeb

jWeb 是 Java web 框架。专注于组件持续集成、快速迭代开发。 写在前面 团队内部使用的一套web开发框架,稳定使用多年,特分享与开源者共勉。 取其精髓,去其糟粕 没有最好,只有最适合,选择...

Steve
2015/08/22
1K
1
自动化运维开发云管理平台--oneops

OneOps 是一个自动化运维开发的云管理平台,它正在主导沃尔玛全球电子商务的技术变革。 OneOps可以在一个平台上管理整个DevOps的工程流程,集成了主流开源DevOps工具(Chef, Nagios, Git,Jenk...

匿名
2016/08/01
3.5K
0
“百川解码”第一期 热修复的坑和阿里的解

热修复是很多开发者关心的技术,8月27日晚,阿里百川组织了“百川解码”在线直播,以“热修复的坑和阿里的解”为主题,邀请了三位业界嘉宾对热修复技术进行了探讨,并介绍了阿里百川全面接受...

阿里百川
2016/09/12
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MySQL 乱七八糟的可重复读隔离级别实现

MySQL 乱七八糟的可重复读隔离级别实现 摘要: 原文可阅读 http://www.iocoder.cn/Fight/MySQL-messy-implementation-of-repeatable-read-isolation-levels 「shimohq」欢迎转载,保留摘要,谢...

DemonsI
57分钟前
2
0
Spring源码阅读——2

在阅读源码之前,先了解下Spring的整体架构: 1、Spring的整体架构 1. Ioc(控制反转) Spring核心模块实现了Ioc的功能,它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描...

叶枫啦啦
今天
1
0
jQuery.post() 函数格式详解

jquery的Post方法$.post() $.post是jquery自带的一个方法,使用前需要引入jquery.js 语法:$.post(url,data,callback,type); url(必须):发送请求的地址,String类型 data(可选):发送给后台的...

森火
今天
0
0
referer是什么意思?

看看下面这个回答(打不开网页可以把网址复制到搜索栏): https://zhidao.baidu.com/question/577842068.html

杉下
今天
1
0
使用U盘安装CentOS-解决U盘找不到源

1. 使用UltraISO制作CentOS安装盘 如果需要安装带界面的系统,为保证安装顺利,可选择Everything版本的ISO制作安装盘。 2. 在BIOS中选择使用U盘安装 系统启动后,进入安装选择界面,其中有三...

Houor
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部