一、前言
在经过了第一阶段的迭代大作业之后,我们迎来了新的迭代大作业-智能电器模拟系统。
在这个阶段的大作业中,我们需要编写模拟家用电器与电路的程序。
二、设计与分析
在经过了第一次迭代大作业的折磨之后,我总结之前出现的问题,在之前的大作业中,我在类的设计方面做的并不好,每当更新一次大作业之后,我总是要做不少的改动,甚至几次直接重写整个项目,这给我带来了不小的麻烦,同时花费了我很多的学习时间,然而结果却总是不如意,几次和满分就相差那么几分,但是却始终得不到。并且在之前的大作业中,我发现每当我过几天再看程序的时候,往往会不记得写的是什么意思,这导致我总需要花一定的时间去阅读之前写的代码,这一点同样花费了我不少的时间,这些即吃里又不讨好,而吸取了上次大作业迭代的经验之后,我在写这次迭代大作业之前先是花了更多的时间去思考如何构建类,并且明晰了类与类之间的关系之后我才真正开始写代码,并且在写代码的过程中,我还时常在代码后面添加注释,以方便后续的阅读和修改,而这次类的设计较为万山也带来了巨大的好处,在写第二次的迭代的时候,相比起第一次迭代,我的代码相比于第一次迭代时提交的代码只多出了30行左右,从第一次迭代的650行到第二次迭代的680行,这也让我体会到了类设计在大项目中的重要性。
这是我在开始写之前大致写的类图
在这个时候我一共设计了12个类,虽然存在一定的问题,但是已经偏向于完整的了,首先我是想着先做一个最大的父类 设备类(把所有电路中的电器元件都视作设备),这样有利于使用多态,不过过多使用继承容易导致一旦父类修改了,子类就很容易需要大改的情况。所以当时我在写这个类的时候花了很多时间进行思考,当然结果是好的,在之后的编写中,这个类基本没有什么修改的。其下面分为了控制器类,例如电压调速器和开关等等。而另一个即是电器抽象类,作为所有电器的父类,而在这个父类之下我又将不同种类的电器的类别分类做了各自的抽象父类,便于后续添加一些同种类的电器,就比如第一次迭代就有的日光灯和白炽灯的区别,不过当然这里其实没必要分得这么细,我这么写其实是考虑到加入之后加入一些比较特殊的电器的情况,所需要设置的属性就不太一样了。除此之外,我在设备抽象类下还写了另一个子类,引脚类,作为每个电路元件的引脚。而右上角的两个类分别为控制类和链接类,前者控制电路的操作,比如指挥链接类将电路中的各个元件进行连接,以及根据输入信息对元件进行修改,比如开关的开关或者连续调速器设置参数。
下面这张图是我在写完了第二次迭代之后的类图
我在其中加入了电路抽象类,其下为串联电路类,并联电路类。并且我把原来设计类图中的设备类之上添加了电子元件类,这考虑到添加的电路类并不算是一个设备,而且将引脚划分为电子元件类会合理一些,同时设备类在程序最后都要输出各自的信息,就可以直接使用多态输出信息了。
比如下面这个输出方法。在设备类中设置一个抽象方法display(),而其子类的所有的控制器以及设备都会输出相关的信息。
public static void show(LinkedList<CircuitMember> members){for(CircuitMember member : members) if (member instanceof Equipment) ((Equipment) member).display();
}
在这个类图中也可以看到我添加了一个电源类,设计这个类的时候我是考虑到假如输入电压不是220v的时候那要根据不同的电源输出不同的电压,虽然这几次迭代大作业中看老师的题目描述感觉不太可能出现这种情况,但是设计了这个之后我觉得这一套类的设计更加合理了一些,并且在设计这个电源类其实有利于我所编写的程序运行,因为我所编写的程序其实最重要的部分就在于不同电子元件的连接上,我的思路是把所有的电路元件连接好之后只要从初始端口输入电压之后,整个程序就会真的跟一条电路一样自行根据设计好的运行方法运行。因此我需要一个电源类作为整个电路的初始输出端口。
powersourse.Run();
所有的电路元件只需要这一行输入电压之后就会自行运行好,所以这个电源类的设计其实会使程序更加合理。
上面这张图是使用SourceMonitor后的检测结果,在这张图中,可以看到LinkCenter类的行数最多,达到了113行,语句数量也达到了97行,因为其中连接电路的部分占了大头,但是其中的注释占比还是不够高,只有10.6%,这也是其中的一个缺陷,其中方法的最大的复杂度达到了9,这个最大的复杂度就出现在了下图的findAllMembers方法中,因为这个方法是通过输入电路信息创建所有电路元件的,其中使用了switch语句根据元件名称的首字母创建对象,因为有9种元件所以复杂度为零,这个地方在之后也应该进行修改降低其圈复杂度。
而其实从整个统计图上来看,最为复杂的地方就是LinkCenter类,其中连接电路部分使用了不少的循环语句和if语句,在日后写的过程中有必要对其进行修改,做出进一步的优化。
其次再看其注释占比例,其实虽然平均下来只有8.6%,但是相比第一阶段的大作业迭代要好了很多了,至少在大部分比较复杂的方法后加上了注释,这至少帮助我在写第二次迭代的时候要方便不少了,至少不至于像之前那样阅读大量源码才能想起来之前的写法了。
三、踩坑心得
1.在第一次迭代之前设计类的时候没有考虑到串联电路和并联电路这种可能,所以在第一次提交的时候遇上了电路种开关在电器之后的情况的时候出了错误,因为我的设计思路是电路从输入端自行运行到最后,当断路开关在电器前面的时候电路就不会往后输入电压了,但是当断路开关在电器后面的时候就无法侦测到这种情况了,就好比一个人走在一条单行道上,当前面有一堵墙(断路开关)的时候,他就往前走不了了,墙之后的蛋糕(电器)他就吃不到了。而如果这堵墙(断路开关)在蛋糕(电器)后面呢,他能先吃到蛋糕之后再遇到墙,对比到电路种这就导致了再电路断开的情况下电器却成功运行了。所幸在我自己设置测试点的情况下,发现了这个问题,于是添加了一个串联电路类处理电路中的电子元件的断路情况,就好比有一个人A是一条路的管理者知道路中的各种情况,一个人B走在这条路上,A会在B走进这条路之前替他看看这条路能不能走,能走B就可以经过这条路上的所有电器,不能走A就阻止B进入这条电路,这样B就不会接触到这些电器,自然就不会出现断路情况下电器运行的情况了。
2.在这套题目的要求情况下,最后程序要按照顺序输出电器的信息,但是这套题目中的电器种类非常多,一共7种,我想通过实现Comparable接口中的compareTo方法实现排序方法,但是如果按照循环的方法写,其复杂程度一定会很高。而且代码行数肯定也不会少,所以我在思考后想到了一种方法
public int compareTo(CircuitMember e) {final String EquipmentName = "KFLBRDA";int index1 = EquipmentName.indexOf(getName().charAt(0));int index2 = EquipmentName.indexOf(e.getName().charAt(0));int result = index1 - index2;if(result == 0) result = getName().compareTo(e.getName());return result > 0 ? 1 : -1;
}
通过设计一个字符串,其中包含所有电器的首字母然后对比两个对象的首字母在字符串中的下标,然后通过比较下标的前后,然后返回1,-1 ,0,当首字母相等的时候,就使用原字符串的compareTo方法返回值,这是为了处理对于当两个对象为同一种设备的情况(设备名字首字母相同,但编号不同.在这种方法下,设备的排序方法就会简单很多,并且只使用到了一个if语句(当下标相同时),同时相比较大量的循环语句而言,这样的代码运行方法会更快,在后续的迭代种如果添加了一些新的设备的时候,就只需要在字符串最后添加新设备的首写字母就可以了,而不是像循环语句中的那种还要添加新的循环语句,从可拓展性而言,远比采用循环方法更好。这一点虽然不算是踩坑点,但是作为一个心得更好。
四、改进建议
我认为可以添加电器短路的情况设计,设置电器短路,当电器短路,电器本身不工作,但电流会通过电器。
五、总结
设计类的时候要考虑更加全面周到:设计类的过程十分重要,这个阶段为后续程序的运行打下基础的部分,所以在设计类的时候,要考虑周到,从自己的思路角度思考代码运行的时候会遇到什么问题,并且该如果在设计类的过程将这个问题给避免掉,就比如踩坑心得中第一点说的忽略了串联电路中开关在设备后面并且断路的情况。
要继续养成添加注释的习惯:持续养成添加注释的习惯,这不仅是在面对大项目的情况下使代码更加通俗易懂的方法,也是对代码进行修改时候的利器
面对一些大循环代码时要思考能不能用另外一种更高效的方法替代:就如心得中第二点中的一样,当面对一些需要多重循环的代码时,要勇于思考一些新的方法去避免这种大循环。