计算机接口的设计与实现过程:
总的设计思路在我的同伴博客:xxxxxxx
那我负责的功能模块主要是GetDic类和GetRes类及Print类这三个类。
print类主要是分辨命令行参数,判断是文本输出还是控制台输出,文本输出则使用StreamWriter这个字符流。
较为关键的则是GetDic类和GetRes类,GetDic类则是使用了dictionary集合生成具有词频的单词集合,那我通过CountWords 所得到的单词集合传入GetDic类遍历集合,第一次的存入集合,重复的词频加一,生成了一个具有词频的单词集合。
流程图
GetRes类则是使用上述getdic所产生的集合进行数据处理,同样是使用dictionary作为结果集合,我优先进行单词集合词频的统计,再由词频在已经排好字典序的单词集合中找,如果符合则添加到我的结果集合中,由Print函数输出
流程图
关键代码展示:
GetDic类:
//传入有countWords使用的单词集合
//得到单词以及对应的数目存入泛型数组keyValues
public static Dictionary<string, int> createDic(StreamReader sr,List<string>words)
{
Dictionary<string, int> keyValues = new Dictionary<string, int>();
//如果这个泛型集合里面如果有这个单词的话就数量增加如果没有这个单词的话就把它加入这个集合
foreach (string s in words)
{
if (keyValues.ContainsKey(s))
{
keyValues[s]++;
}
else
{
keyValues.Add(s, 1);
}
}
return keyValues;
}
GetRes类:
//将单词进行词频排序并且输出前n个词频的单词
public static void SortKey(Dictionary<string, int> keyValues, Dictionary<string, int> result, int count)
{
//对该集合进行字典序排序
keyValues = keyValues.OrderBy(o => o.Key, StringComparer.Ordinal).ToDictionary(p => p.Key, o => o.Value);
//单词频数集合
List
foreach (int i in keyValues.Values)
{
value.Add(i);
}
//进行单词频数排序
value.Sort((x, y) => -x.CompareTo(y));
//如果参数是-1 则默认输出前10个
if (count == -1)
{
count = 10;
}
//次数变量
int index = 0;
foreach (var s in keyValues)
{
//找出单词频数为最高的单词
if (s.Value.Equals(value[0]) && index <= count)
{
//提取对应的单词以及出现的频数
result.Add(s.Key, value[0]);
index++;
}
}
//顺序提取单词频数前10的单词 同频的按照字典序排列
for (int i = 1; i < value.Count && index <= count; i++)
{
if (value[i] == value[i - 1])
continue;
foreach (var s in keyValues)
{
if (s.Value.Equals(value[i]))
{
if (index < count)
{
//按照制定格式输出对应的写入流中
result.Add(s.Key, value[i]);
index++;
}
else
break;
}
}
}
}
四大原则体现:
Design By Contract:
调用上述方法时对文件路径有一定要求,我设置了以.txt格式的文本来作为数据源,那如果不是文本的话,则会提示错物信息
Information Hiding:
那每个模块的功能是独立的,即使我设置为了静态方法,但是在CountWords的时候使用了正则表达式,具体的正则表达式对于其他模块来讲,应该时封闭的,因此我设置了关于正则表达式的数据时私有的。
Interface Design:
按照功能模块定义为接口或者接口功能的名字,任意接口之间没有什么直接的联系,如直接调用等聚合方式,但是在参数部分,需要使用其他接口调用的结果。
Loose Coupling :
各模块之间可独立运行,在第一版中我们时使用了一个工具类包括了这些方法,同样的使用了相同的静态数据来存储结果,在和同伴讨论后,发现模块之间联系的过于紧密,因此将具体方法分类,模块独立使用。增强他的松耦合。
代码复审过程:
本次的代码经过了自审及互审的过程,完善了一些不足。自审过程则发现了流的释放问题及代码注释问题,并且感觉自己的接口设计有缺陷,重复使用了StreamReader类,那我将streamreader写主程序中,以参数方式使用,节省空间。
那在互审过程中,我发现了同伴的代码问题
应该使用\W 表示的非英文数字字符,那\w 表示的是英文字符如果使用上文中的,则是分割了字母,而不是单词,会导致程序可以运行,但是结果异常。
接口部分性能改进:
单元测试展示:
测试函数-CountChar(统计字符类)
思路:读取一个简单的文档,使用断言判断字符串长度是否等于自己的预期
测试函数-CountLine(统计行数类)
思路:读取一个简单的文档,使用断言判断其行数是否等于自己预期
测试函数CreatWords(统计单词类)
思路:同样是读取一个文件 得到起文本字符串 ,使用测试函数生成单词词组,判断单词的个数。
测试函数-GetDic(生成单词)
思路:本想使用读取文件,但是速度较慢,为验证功能,我摸拟了一个单词列表,借由此单词列表测试被测试函数,可以测试出对应单词的频数符合自己的预期
测试函数-GetRes(统计词频前n并添加到结果集合)
思路:读取文档生成了一个dictionary的单词集合,借由集合测试被测试函数,会输出到前10的单词 至控制台中,结果如下
测试函数-WordGroup(词组输出)
思路,读取文本文件,得到其单词数量list,测试被测试函数,输出以三个为一组的单词,将测试结果输出至控制台
全部测试通过
计算模块部分异常处理说明
异常处理机制,忽略因代码问题所产生的异常,我在这里主要考虑的就是有关文件读取方面的相关异常,那我设计了几个相关的操作,其中文件格式和未输入文件是通过if else 在主方法前进行判断的,数据的来源则是最重要的,那有了正确的输入文件和格式,但是路径失败会导致读取不到相应的文件,因此我使用了try catch 语句进行异常的捕获,具体如下
这个是判断路径及格式问题,测试用例如下:
场景则是防止在用户输入错误的文档格式或者忘记输入文档路径进行的处理机制,否则会引发程序中的异常。
这个则是在streamReader读取文档时因检索不到本地资源导致抛出异常,异常会被catch捕获,输出提示信息并中断程序。
场景则是用户输入了正常的格式及文档路径,但是在本地没有这个资源文件而导致的异常。