WorldCount 一个文本内容简单查询的工程
目录
项目源码
https://gitee.com/guilinyunya/WorldCount
一、PSP
PSP2.1 | PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Panning | 计划 | 60 | 100 |
.Estimate | .估计这个任务需要多少时间 | 60 | 100 |
Development | 开发 | 2448 | 3600 |
.Aanlysis | .需求分析(包括学习新技能) | 30 | 40 |
.Design Spec | .生成设计文档 | 60 | 180 |
.Design Review | . 设计复审 (和同事审核设计文档) | 30 | 40 |
.Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
.Design | .具体设计 | 90 | 130 |
.coding | .具体编码 | 2000 | 2890 |
.Test | . 测试(自我测试,修改代码,提交修改) | 218 | 300 |
Reporting | 报告 | 150 | 160 |
.Test Report | . 测试报告 | 60 | 40 |
.Size Measurement | .计算工作量 | 60 | 60 |
.Postmortem & Process Improvement Plan | .事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 3198 | 3860 |
二、项目简介
- 本项目主要实现一个对代码源文件统计查询的控制台程序。
- 实现语言:java
- 该项目实现的主要功能有 :
- 源文件字符数的统计
- 源文件单词数的统计
- 源文件行数的统计
- 结果输出
- 选项: [parameter] [agrument]
- [-o : arg ] 结果输出位置
- [-w ] 单词数的统计
- [-l ] 行数的统计
- [-c ] 字符数的统计
- 项目自定义:
- **选项 [ -f ] **:用于说明输入文件;当命令无该选项时,默认最后面的参数为输入文件
- 选项等级 :当控制台同时输入多个选项及其参数时,选项等级规定了选项功能执行的顺序,等级越高功能越执行。选项等级如下:
- 等级一:[ -f ]
- 等级二:[ -c ] [ -l ] [ - w ]
- 等级三:[ -o ]
- 注:等级数字越小等级越高,这里的选项等级只是针对该项目当前的功能,项目扩展功能后选项等级要改变。
三、项目分析与实现
思路概述
该项是一个控制台应用程序,结合项目的要求,我将项目实现分为四个部分:
- 程序入口和出口:WorldCount类。该类主要用于接受用户的输入和处理结果输出。
- 选项、参数解析:Analyzer类。该类主要用于分析参数和处理参数的缺省,错误等问题。
- 执行、处理:Processor类。该类接受Analyzer类消息,并根据消息执行相应的功能。
- 数据格式化:Format类。该类接受Processor类的处理结果,并对结果进行格式化处理。
- UML类简图
(由于UML没有系统的学习过,该图可能存在不足 >--<)
思路详解
WorldCount类
- 说明:
- 该类是程序的入口和出口,接受输入的选项和参数,输出处理结果。
- 该类主要维护对象:
private ArrayList<String> inputInfo = null; //输入字符串数组,储存用户输入的数据
private Analyzer analyzer = null; //参数分析器
private Processor processor = null; //文件处理器
private Format format = null; //数据格式化
- 主要方法:
public void printInfo(ArrayList<String> outputInfo, File file) ; //用于打印错误信息或处理的结果
public void startActive() ; //开始文档解析工作
public static void main(String[] args) ;
- 逻辑:
- main方法将选项、参数放到 inputInfo 里,然后执行 startActive() 方法执行相应功能,最后执行 printInfo() 方法输出结果。
- 主要方法实现:
Analyzer类
- 说明:
- 该类主要用于分析用户传入的选项、参数,通过 Filter 接口的相应实现类对选项和参数进行分析和筛选,然后 Aanalyzer 对象进行分析结果收集。
- 该类主要维护对象:
private static ParseItems parseItems = null; //分析用到的数据条目,里面包含了该程序的所有选项及其相关信息
private ArrayList<String> paras = null; //选项、参数储存对象
- 主要方法:
public boolean parse(final HashMap<String, CommandItem> commandInfo); //执行选项和参数解析工作
- 逻辑:
- Analyzer 对象接收 WorldCount 对象传来的选项、参数数据,进行数据更正;然后,执行 parse() 方法进行数据解析;最后通过方法参数 map 返回结果。
- 主要方法实现:
Processor类
- 说明:
- Processor 类接收 Analyzer 类的分析结果 HashMap<String, CommandItem> 对象, 然后执行 execute() 方法并返回结果。
- 该类主要维护对象:
public static String[][] commandOrder = null; //设置CommandItem类对象填入command数组的顺序。ParseItems.PARAMETER_f和ParseItems.PARAMETER_o单独存在
private HashMap<String, CommandItem> commands = null; //选项处理类类名的储存器
- 主要方法:
public HashMap<String, String> execute() ; //对文本执行相应操作
- 逻辑:
- 在 execute() 方法中:一、首先利用 commands 里存有的选项功能类(就是Command类)的类名,然后通过反射获得选项功能类的实例并设置参数。二、通过
commandOrder 里面的选项执行顺序进行功能类对象嵌套。三、最后一次调用各个功能类对象的 parse() 方法。
- 在 execute() 方法中:一、首先利用 commands 里存有的选项功能类(就是Command类)的类名,然后通过反射获得选项功能类的实例并设置参数。二、通过
- 主要方法实现:
Format类
- 说明:
- Format 类接收 Processor 类执行结果 HashMap<String, String> 对象, 然后执行 format() 格式化结果。
- 该类主要维护对象:
public String className = "format.FormatSimple"; //指明使用哪个类进行结果格式化
- 主要方法:
public void format(HashMap<String, String> map); //格式化结果
- 主要方法实现:
Command抽象类
- 说明:
- Command类主是所有选项功能类的父类,该类定义相应的方法,子类具体实现。同时该类有一个指向自身的集合对象,用于实现选项功能的嵌套。
- 该类主要维护对象:
private ArrayList<Command> comms = null; //自身容器对象,用于用于实现选项功能的嵌套。
- 主要方法:
public void analyse(String info);//分析文本,必须重写。建议放在代码第一行调用super.analyse(),否则通过调用super.getCommands()来获取子命令,在调用之命令的 analyse() 方法.
public void getInformation(HashMap<String, String> map) ;//获得分析结果,必须重写。建议放在代码第一行调用super.getInformation(),否则通过调用super.getCommands()来获取子命令,在调用之命令的 getInformation() 方法.
public abstract void setArgument(String arg); //传入参数
- 主要方法实现:
Formative接口
- 说明:
- Formative 接口定义一个结果格式化的方式,该接口里有一个 format() 子类实现具体格式方法。
- 主要方法:
public void format(HashMap<String, String> map);
Filter接口
- 说明:
- Filter接口定义一个选项的参数的过滤方式,该接口里有一个 filter() 子类实现具体格式方法。
- 主要方法:
public String filter(String para);
CommandItem类
- 说明:
- CommandItem类用于储存选项的功能类的类名和参数
- 该类主要维护对象:
private String className = null; //命令执行类名
private String argument = null; //命令参数
ParseItems类
- 说明:
- ParseItems类用于储存选项的相关信息
- 内部类:
/*
* 储存选项参数数据项
*/
private static class Item{
public String parameter = null; //参数名
public String filterClass = null; //过滤器类名
public String className = null; //参数功能执行类名
public Item(String para, String filter, String className) {
this.parameter = para;
this.filterClass = filter;
this.className = className;
}
}
- 该类主要维护对象:
public static final String PARAMETER_o = "-o"; //选项 -o
public static final String PARAMETER_w = "-w"; //选项 -w
public static final String PARAMETER_l = "-l"; //选项 -l
public static final String PARAMETER_f = "-f"; //选项 -f
public static final String PARAMETER_c = "-c"; //选项 -f
private static HashMap<String, Item> items = null; //选项数据项集合
四、测试过程
测试思路
- 每一个类独立的生成一个测试类,进行独立测试,并且对类里的主要方法编写测试方法。测试类的类名为被测试者的类名加上“Test”后缀组成,每个测试的测试用例都在二以上。测试用例设立都是正确输入和错误输入二者都有,除了部分类调用toString()方法做测试的类。
测试浏览
-
WorldCountTest 类:
-
AnalyzerTest 类:
-
ProcessorTest 类:
-
OutputFileFilterTest 类:
-
ParseItemsTest 类:
-
CommandCTest 类:
-
CommandFTest 类:
-
CommandLTest 类:
-
CommandOTest 类:
-
CommandWTest 类:
五、心得体会
-
项目感悟:
通过这次项目实践,我体会到了做项目计划的重要性。以前做项目都是编写边改的模式,到最后项目要添加新功能,为了添加新功能,每次都是伤筋动骨的改动,耗时又费精力。(那滋味酸爽!)做这个项目时,我总体按照了软件的开发流程,做了项目计划。在做项目设计时,我的第一想法是这个项目可以分成及部分来实现(虽然在编码实现时又改了一下设计,但是都不伤筋动骨 ( ̄▽ ̄)~*),然后才是考虑程序功能扩展的容易性。
对于项目设计我分为四部分,在前面我有详细说明(点击查看)。对于程序功能的扩展的容易性,我使用的是外观模式和职责链模式来增加程序扩展的容易性。比如选项、参数解析过程,我将这个过程分为选项纠正、选项查询、参数过滤和生成结果四个过程。纠正、查询和生成结果这些都是所有选项共有的,只有参数过滤是不同,所以我使用了定义了一个Filter接口写或这个过程,增强选项、参数扩展的容易性。最后,我用一个Analyzer类把选项、参数解析的具体组成部分包裹起来,这样再增加新的选型时,我只再需要继承Filter接口写一个对应的参数过滤类就可以了。Processor类也是同样的思想。 -
灵感乍现:
通过这次项目,我个人认为我以后再实现项目前都可以先设计命令,根基命令来实现相应的功能。如窗体类程序,程序窗体部分和具体功能部分可以用命令来连接,实现前后部分的分离,使项目改变更灵活、快捷。
运行结果
-
a.txt,输入文件:
-
结果:
-
情况一:
-
情况二:
-
情况三:
-
情况四:
(这个bug在我的version1.2版本中得到更正啦( ̄▽ ̄)/)
返回目录 -
七、实现
代码实现
WorldCount类
点击返回
/*
*用于打印错误信息或处理的结果
*ouputInfo : 打印的信息
*file : 打印信息的输出文件。当为null时,者输出到控制台
*/
public void printInfo(ArrayList<String> outputInfo, File file) {
OutputStream output = null;
if(file != null) {
try {
output = new FileOutputStream(file, true);
for(String str : outputInfo){
output.write(str.getBytes());
}
}
....
}else {
for(String str : outputInfo){
System.out.println(str);
}
}
inputInfo.clear();
}
/*
*开始文档解析工作
*/
public void startActive() {
...
//commandInfo的类型是HashMap<String, CommandItem>
if(analyzer.parse(commandInfo)) { //判断选项及其参数是否正确
HashMap<String, String> resualtInfo = null; //保存文档分析结果
processor.setCommands(commandInfo); //设置命令消息
resualtInfo = processor.execute(); //执行分析,并返回分析结果
if(resualtInfo.get(ParseItems.PARAMETER_o) != null) //是否有 -o 选项输入
file = new File(resualtInfo.get(ParseItems.PARAMETER_o));
format.format(resualtInfo); //格式化数据
Iterator<String> itr = resualtInfo.values().iterator();
//arr 为 ArrayList<String> 类型
//设置结果到 ar里
while(itr.hasNext()) {
arr.add(itr.next());
}
}else {
//设置选项、参数分析错误结果
arr.add(commandInfo.get(Analyzer.PARSES_ERROR_FLAGS).getArgument());
}
printInfo(arr, file); //打印结果
}
/*
*程序入口,设置输入选项、参数
*/
public static void main(String[] args) {
WorldCount worldCount = new WorldCount();
if(args.length != 0) { //判断是否有选项、参数输入
for(String str : args) {
worldCount.addInputInfo(str); //添加选项和参数
}
worldCount.startActive(); //执行文档解析工作
}else {
//无选项参数输入是打印错误消息
ArrayList<String> result = new ArrayList<String>();
result.add("Error: 无文件输入");
worldCount.printInfo(result, null);
}
}
Analyzer类
点击返回
/*
*/执行选项和参数解析工作
*commandInfo : 用于储存解析后的结果
*return boolean : 用于标志解释有无错误。true 表示无错,false 表示有错。
*/
public boolean parse(final HashMap<String, CommandItem> commandInfo) {
...
Iterator<String> iterator = paras.iterator(); //返回选型迭代器
while(iterator.hasNext()) {
para = iterator.next(); //获取选项或参数
if(parseItems.contains(para)){ //判断 para 是不是选项
String filterName = parseItems.getFilterClass(para); //获取选项 para 的 Filter 类名
arg = null; //arg 为String 类型,用于暂存 para 的参数
if(filterName != null) { //判断是否存在 para 的Filter 类, Filter类存在表示该选项必须要输入一个参数,不存在表示该选项不能有参数
if(iterator.hasNext() && parseItems.contains((arg = iterator.next()))) { //获取和判断该选项的参数是否存在
/*
*选项参数无
*/
builder.append(para); //builder 为 StringBuilder 类型
builder.append(" : 后无参数");
break;
}
try {
Filter filt = (Filter) Class.forName(filterName).newInstance(); //获取 para 的 Filter 类的实例
String result = filt.filter(arg); //检测 para 类的参数是否合法,并返回信息。参数合法,返回 null ;不合法,返回对应错误信息
if(result != null){
/*
*选项参数不正确
*/
builder.append(para);
builder.append(" : ");
builder.append(result);
break;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
CommandItem item = new CommandItem(parseItems.getClassName(para), arg); //把通过分析的选项及其相关信息放入 CommandItem 里
commandInfo.put(para, item); //放入返回结果容器里
}else {
/*
* 选项不正确
*/
builder.append(para);
builder.append(" : 不正确选项");
break;
}
}
//当分析出错后,清空之前的分析结果,放入错误消息,并返回
if(builder.length() != 0) {
commandInfo.clear();
CommandItem item = new CommandItem(PARSES_ERROR_FLAGS, builder.toString());
commandInfo.put(PARSES_ERROR_FLAGS, item);
return false;
}
return true;
}
Processor类
点击返回
/*
*对文本执行相应操作
*return HashMap<String, String> : 返回处理结果
*/
public HashMap<String, String> execute() {
HashMap<String, String> map = new HashMap<String, String>(); //返回结果容器
ArrayList<Command> comms = new ArrayList<Command>(); //同等级选项功能类对象临时容器
Command comm = new Command() {
public void setArgument(String arg) {}
public String getName() {return null;}};
//根据功能的执行顺序(commandOrder)对 Command 类进行实例化
for(int i = 0; i < commandOrder.length; ++i) {
String[] order = commandOrder[i]; //从 commandOrder 中取出同一等级的选项
boolean flag = true; //上一次同一等级选项功能类对象是否初始化完毕标志
for(int j = 0; j < order.length; ++j) {
String ordert = order[j]; //取出选项
if(commands.containsKey(ordert)) { //判断选项 ordert 在用户输入选项中是否存在
try {
//通过反射获的 Command 的对象
CommandItem item = commands.get(ordert);
Command commt = (Command) Class.forName(item.getClassName()).newInstance();
commt.setArgument(item.getArgument()); 给 Command 对象设置参数
if(flag) { //上一次同一等级选项功能类对象是否初始化完毕
for(Command comtt : comms) {
commt.addCommand(comtt); //将上一等级的对象嵌套
}
flag = false;
comms.clear(); //清空临时容器
}
comms.add(commt);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
}
//将 comms 剩余的 Command 嵌套进 comm 里
for(Command comtt : comms) {
comm.addCommand(comtt);
}
comms = null;
comm.analyse(null); //执行 analyse 工作
comm.getInformation(map); // 获取执行结果
return map;
}
Format类
点击返回
/*
*格式化结果
*map : 要格式化的结果
*/
public void format(HashMap<String, String> map) {
try {
Formative format = (Formative)Class.forName(className).newInstance(); //实例化格式化对象
format.format(map); //格式化结果
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
Command抽象类
点击返回
/*
*分析文本,必须重写。建议放在代码第一行调用super.analyse(),否则通过调用super.getCommands()来获取子命令,在调用之命令的 analyse() 方法.
*infor : 要分析的信息
*/
public void analyse(String info){
//调用嵌套命令的 analyse() 方法
if(comms != null) {
for(Command comm : comms) {
comm.analyse(info);
}
}
}
/*
*获得分析结果,必须重写。建议放在代码第一行调用super.getInformation(),否则通过调用super.getCommands()来获取子命令,在调用之命令的 getInformation() 方法.
*map : 获得分析的结果
*/
public void getInformation(HashMap<String, String> map) {
//调用嵌套命令的 analyse() 方法
if(comms != null) {
for(Command comm : comms) {
comm.getInformation(map);
}
}
}
测试用例实现
WorldCountTest 类:
点击返回
public static void worldCountTest() {
...
String[][] info = {{"-o", "fddsf", "./src/res/test.txt", "-c"},{"-o", "C:\\", "-c", "./src/res/test.txt"},{"-o", "-c", "./src/res/test.txt"},{"-c", "./src/res/test.txt"},{"-c", "./src/res/test1.txt"}};
...
}
public static void worldCountTest2() {
String[][] info = {{"-w", "-l", "-c", "-o", "C:\\Users\\28561\\Desktop\\a.txt", "./src/res/test.txt"}};
...
}
AnalyzerTest 类:
点击返回
public static void fieldTest() {
ArrayList<String> paras = new ArrayList<String>();
paras.add("-o");
paras.add("C:\\file.txt");
paras.add("inputFile.txt");
...
}
public static void correctParametersTest() {
ArrayList<String>[] paras = new ArrayList[3];
paras[0] = new ArrayList<String>();
paras[0].add("-o");
paras[0].add("C:\\file.txt");
paras[0].add("inputFile.txt");
paras[1] = new ArrayList<String>();
paras[1].add("-c");
paras[1].add("C:\\file.txt");
paras[1].add("-f");
paras[1].add("inputFile.txt");
paras[1].add("inputFile.txt");
paras[1].add("inputFile.txt");
paras[1].add("inputFile.txt");
paras[2] = new ArrayList<String>();
paras[2].add("-o");
paras[2].add("C:\\file.txt");
paras[2].add("inputFile.txt");
paras[2].add("-f");
...
}
public static void parseTest() {
ArrayList<String>[] paras = new ArrayList[4];
paras[0] = new ArrayList<String>();
paras[0].add("-o");
paras[0].add("C:\\file.txt");
paras[0].add("inputFile.txt");
paras[1] = new ArrayList<String>();
paras[1].add("-c");
paras[1].add("C:\\file.txt");
paras[1].add("-f");
paras[1].add("inputFile.txt");
paras[2] = new ArrayList<String>();
paras[2].add("-o");
paras[2].add("C:\\file.txt");
paras[2].add("inputFile.txt");
paras[2].add("-f");
paras[3] = new ArrayList<String>();
paras[3].add("-o");
paras[3].add("C:\\index");
paras[3].add("C:\\Program Files\\desktop.ini");
...
}
ProcessorTest 类:
点击查看
public static void processorTest() {
String[] strs = {"-o", "C:\\test.txt", "-c", "./src/res/test.txt"};
ArrayList<String> arr = new ArrayList<String>();
for(int i = 0; i < strs.length; ++i)
arr.add(strs[i]);
Analyzer ana = new Analyzer(arr);
HashMap<String, CommandItem> mapi = new HashMap<String, CommandItem>();
boolean b = ana.parse(mapi);
...
}
public static void setInformationTest() {
HashMap<String, CommandItem> mapi = new HashMap<String, CommandItem>();
mapi.put("-o", new CommandItem("processor.CommandO", "C:\\test.txt"));
mapi.put("-f", new CommandItem("processor.CommandF", "./src/res/test.txt"));
...
}
OutputFileFilterTest 类:
点击查看
public static void filterTest() {
String[] paths = {"adffkdfj", ".", "C:\\Program Files\\desktop.ini"};
String result = null;
OutputFileFilter filter = new OutputFileFilter();
for(int i = 0; i < paths.length; ++i) {
result = filter.filter(paths[i]);
System.out.println("path : " + paths[i]);
System.out.println("result : " + result);
System.out.println();
}
}
ParseItemsTest 类:
点击查看
public static void toStringTest() {
String str = new ParseItems().toString();
System.out.println("Test : ParseItemsTest \n" + str);
}
CommandCTest 类:
点击查看
public static void analyseAndGetInformationTest() {
CommandC comm = new CommandC();
comm.addCommand(null);
String[] strs = {"Hello world", ""};
...
}
public static void commandTest() {
CommandC com = new CommandC();
CommandC com2 = new CommandC();
com.addCommand(com2);
String str = "Hellow World";
System.out.println("argu : " + str + " length : " + str.length() + "\n");
com.analyse(str);
HashMap<String, String> map = new HashMap<>();
com.getInformation(map);
...
}
CommandFTest 类:
点击查看
public static void analyseAndGetInformationTest() {
CommandF cof = new CommandF();
CommandC coc = new CommandC();
cof.addCommand(coc);
String path = "./src/res/test.txt";
cof.setArgument(path);
cof.analyse(null);
HashMap<String, String> map = new HashMap<>();
cof.getInformation(map);
...
}
CommandLTest 类:
点击查看
public static void analyseTest() {
String[] str = {"Hello world", " Hello wode world "};
CommandL cow = new CommandL();
...
}
CommandOTest 类:
点击查看
public static void analyseAndGetInformationTest() {
CommandO coo = new CommandO();
CommandC coc = new CommandC();
coo.addCommand(coc);
coo.setArgument("./src/res/test.txt");
coo.analyse("Hello world");
HashMap<String, String> map = new HashMap<>();
coo.getInformation(map);
...
}
CommandWTest 类:
点击查看
public static void analyseTest() {
String[] str = {"Helloworld", " Hello wode world "};
CommandW cow = new CommandW();
...
}