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() 方法。
  • 主要方法实现:

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()方法做测试的类。

测试浏览

返回目录



五、心得体会

  • 项目感悟:
    通过这次项目实践,我体会到了做项目计划的重要性。以前做项目都是编写边改的模式,到最后项目要添加新功能,为了添加新功能,每次都是伤筋动骨的改动,耗时又费精力。(那滋味酸爽!)做这个项目时,我总体按照了软件的开发流程,做了项目计划。在做项目设计时,我的第一想法是这个项目可以分成及部分来实现(虽然在编码实现时又改了一下设计,但是都不伤筋动骨 ( ̄▽ ̄)~*),然后才是考虑程序功能扩展的容易性。
    对于项目设计我分为四部分,在前面我有详细说明(点击查看)。对于程序功能的扩展的容易性,我使用的是外观模式和职责链模式来增加程序扩展的容易性。比如选项、参数解析过程,我将这个过程分为选项纠正、选项查询、参数过滤和生成结果四个过程。纠正、查询和生成结果这些都是所有选项共有的,只有参数过滤是不同,所以我使用了定义了一个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();
		...
	}

返回目录

posted @ 2018-09-25 14:11  GUI_LIN  阅读(648)  评论(0编辑  收藏  举报