OO第三次博客作业---透过代码看设计
不得不说的JSF
经过前几次作业的煎熬。出租车的代码量不断地增多。而出租车问题在不断的完善,这也就牵涉到一个问题,那就是最初出租车程序的设计问题,如果一开始设计的就有问题的话,那么在后来的过程中就会遇到更多的麻烦。就像微软不断的给自己的系统打补丁一样。但是如果某一天他发现系统有一个关键性的设计错误。虽然这种几率较小,但是一旦发生,将会带来巨大的灾难,https://baike.baidu.com/item/%E5%8D%83%E5%B9%B4%E8%99%AB/2954?fr=aladdin。2000发生的千年虫事件就是一个例子。所以说,设计是代码的基础,对于大型项目来说设计显得更为重要。
设计是如此的重要,就体现了JSF的重要性了。软件发展走到今天,代码俨然进入了开源时代。Github造福千万码农。但是一拿到别人的代码,除了天才,大型项目的代码也是难以一眼就看个明白吧。这里java中的JSF就发挥了作用了。有了JSF就可以很容易的理解java中方法的功能,对理解代码,与看懂设计者的目的有很大的帮助。所以我们需要多多阅读JSF来提高自己的设计水平,(*^_^*),:D。是这样的吗?我觉得JSF的初衷是这样的,但是…………,可能是由于JSF本身作为一种难以十分明确的评测标准,可能会导致同学之间评测的时候有小小的差异。有时候还会很大,super 大。JSF真的很有用,惭愧的是,我都没有好好写JSF,基本都是中文草草解决了,后来才知道,已经不允许用中文了吗?≡(▔﹏▔)≡。庆幸的是,我的人品还行,这么多JSF,就只被报了一个Crash,实在很感谢那位同学了,☆⌒(*^-゜)v THX!!
出租车完善第一弹,加流量
为了让DD出租车变得更加优秀。此次特地增加了DD打鬼出租车功能。Load FileName的设计功能。如果需要满足客户的话,那么必须是实时的,也就是说,所有的系统必须满足能够实时的处理问题。但是对于Load Filename的实时处理确实会带来一些问题。这一功能也体现了我上一次代码的不足。在设计层面上处理输入问题的失误。对于输入的问题,现在输入变的多样化了,那么输入的处理不是简单的只处理乘客的请求了。这让我参悟关于输入的问题。之前的的所有的程序中,我们都是直接把输入看作是请求,或者是任务。也就是说,在程序开始之前,输入就是我们某个指定的东西了,但是现在,我们发现,输入就是一个输入,不要在程序之外就定义输入是什么,那样就相当于把输入的概念缩小了。所以我们处理输入也是在程序之中处理输入。
public synchronized void run() {
//Requires: 无
//Modifies: in_input
//Effect:对输入判断,判断输入是什么类型的
//THREAD_REQUIRES:无
//THREAD_EFFECT:输入线程,就是对输入判断的线程
String in_input=null;
Scanner scanner = new Scanner(System.in);
Request request_in = new Request(new_taxi, new_map, new_gui); //对于请求类,就是输入是请求而不是其他
Big_change big_change = new Big_change(); //对于大规模的改变创建对象
Test test = new Test(); //对于
Change_road road_ch = new Change_road();
in_input = scanner.nextLine();
//in_input = in_input.replaceAll(" ","");
while(true) {
if(in_input.equals("END")) {
break;
}
else if(in_input.equals("Load D:\\est.txt")) {
big_change.change(new_taxi, new_map, new_gui, in_input);
}
else if(in_input.matches(str_road)) {
road_ch.road_change(new_map, new_gui, in_input);
}
else if(in_input.matches(str_test)) {
test.test_out(new_taxi, in_input);
}
else
{
request_in.input(in_input);
}
in_input = scanner.nextLine();
//in_input = in_input.replaceAll(" ","");
}
scanner.close();
return ;
}
请不要太在意JSF,它的初衷是让你知道我这段代码在干什么,ο(=•ω<=)ρ⌒☆
另一个设计上的问题就是关于出租车随机走的问题。出租车在随机行驶的过程中也需要走流量最小的那一条边。我尝试过很多种方法。在第一次作业中侥幸没有被检测到bug。但是到了下一次作业就难逃发掌了。首先解决流量问题势必会增加出租车随机走时行走一步的时间,但是之前采用的方案是,while循环使用随机数。由于随机数的不确定性,导致走一步的时间差距还是蛮大的。后来直接暴力遍历四条边,也就是说,四条边都遍历一下,然后再判断走哪一条边,这样每走一步,时间大致是相同的。对,走到这里还没什么问题。但是我疏忽了另一个问题。那就是之前流量的刷新都是单独一个线程的。那个线程的时间是500ms。现在我每走一步的时间根本不可能掐到500ms。也就是说毫无疑问的错了。流量1还没拿来计算可能就被清空了吧。所以,那就改吧。由于时间是一个玄学的问题。“当你站在另一个角度的时候,时间总是在流逝”。所以我们就用假时间吧。流量也改吧,每走一步清空上一步的流量,然而这也带来了问题,那就是内存问题。
for(int i=1; i<=4; i++) {
if(direction==1) {
if(this.location_x<79 && map_mess.graph[location][location_1]==1 && (tra_wan[1]<=tra_wan[wan_flag] || wan_flag==0)) {
wan_tem_x = this.location_x+1;
wan_tem_y = this.location_y;
wan_flag = 1;
}
direction = 2;
this.Taxi_Direct = 1; //表示如果向东行驶
continue;
}
else if(direction==2) {
if(this.location_x>0 && map_mess.graph[location][location_2]==1 && (tra_wan[2]<=tra_wan[wan_flag] || wan_flag==0)) {
wan_tem_x = this.location_x-1;
wan_tem_y = this.location_y;
wan_flag = 2;
}
direction = 3;
this.Taxi_Direct = 3; //表示如果向西行驶
continue;
}
else if(direction==3) {
if(this.location_y>0 && map_mess.graph[location][location_3]==1 && (tra_wan[3]<=tra_wan[wan_flag] || wan_flag==0)) {
wan_tem_y = this.location_y-1;
wan_tem_x = this.location_x;
wan_flag = 3;
}
direction = 4;
this.Taxi_Direct = 4; //表示如果向北行驶
continue;
}
else if(direction==4) {
if(this.location_y<79 && map_mess.graph[location][location_4]==1 && (tra_wan[4]<=tra_wan[wan_flag] || wan_flag==0)) {
wan_tem_y = this.location_y+1;
wan_tem_x = this.location_x;
wan_flag = 4;
}
direction = 1;
this.Taxi_Direct = 2; //表示如果向南行驶
continue;
}
}
设计上顾全大局还是很有难度的。追求速度的时候,必然需要损失一定的内存。这是无法避免的。我们将流量整合到出租车运行中,在内存上的开销肯定会有所增大。因为我在这里用的是静态数组,而不是动态数组。所以这个设计问题还是难以处理。但是最好还是多用动态数组,最好不要用静态数组,因为你永远不知道对面是怎样测你的程序的。`(*>﹏<*)′
出租车完善第二弹,红绿灯
这一次的内容是加上红绿灯。刚才提到的多线程之间的时间问题导致的流量问题与这一次发现的另一问题有着异曲同工之妙…………。( *^-^)ρ(*╯^╰) 在设计红绿灯的时候,我们需要等红绿灯。而我们将红绿灯的控制放在了单独的线程中,那么就会导致我们不得不同时用两个线程处理数据。也就是供给与需求的冲突问题。当然,这里对红绿灯的处理,只有红绿灯管理线程改变红绿灯的颜色,而出租车线程仅仅读取红绿灯的颜色,并不会改变之。当我们在十字路口等待红绿灯时候,出租车线程应该是怎么样的呢?
if((this.Old_Direct==2 && this.Taxi_Direct==2) || (this.Old_Direct==4 && this.Taxi_Direct==4) || (this.Old_Direct==1 && this.Taxi_Direct==2) || (this.Old_Direct==3 && this.Taxi_Direct==4)) {
while(guigv.lightmap[this.location_y][this.location_x]==1) { //这时需要等红灯
;
}
}
这是之前的错误代码?同学们有没有发发现这个代码错误的地方呢?
我测了好久才发现问题,(′д` )…彡…彡
只要遇到红绿灯,如果需要等待的话,程序就会进入死循环。对是死循环,也就是说程序检测不到红绿灯的变化吗?
错!!!是红绿灯根本就没有机会改变颜色,因为在Java中,多线程是指线程之间的调度执行,多个线程之间进行切换。但是实际上,CPU中运行的线程只有一个,这样写带来的问题就是,由于while()循环实在是太快了,中间没有任何的停顿,假设我们需要切换到控制红绿灯的线程,但是由于while()循环几乎没有延迟的就要访问红绿灯,这个访问时间,远比改变红绿灯的时间短,那么控制红绿灯的线程根本就没有运行。所以就导致红绿灯无法改变,只能切换到其他线程,不能进入红绿灯线程。出租车就会一直停在等待红绿灯的地方。
更改之后应该是这样的。
if((this.Old_Direct==2 && this.Taxi_Direct==2) || (this.Old_Direct==4 && this.Taxi_Direct==4) || (this.Old_Direct==1 && this.Taxi_Direct==2) || (this.Old_Direct==3 && this.Taxi_Direct==4)) {
while(guigv.lightmap[this.location_y][this.location_x]==1) { //这时需要等红灯
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
我们等待红绿灯的线程。说到底就是等待时间,所以在这种情况下,我们在红绿灯控制中计算好这样一个时间,那就是下次红绿灯改变的时间。
while(true) {
try {
Thread.sleep(guigv.Light_time);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0; i<80; i++) {
for(int j=0; j<80; j++) {
Light_Change(i,j);
}
}
guigv.Light_Still = System.currentTimeMillis() + guigv.Light_time;
}
这样,如果遇见红绿灯需要等待,只需要等待至下次红绿灯改变的时候即可。
出租车完善第三弹,VIP车
这里我们我们将他叫做VIP车,实际上是可追踪出租车。但是我觉得老师提出可追踪出租车的缘由并不是可以让他走已关闭的道路,同时可以记录出租车艰辛的路途。而是在检测我们的设计的问题。对设计而言是十分重要的。在这里我直接上我的程序设计文档吧。写得不好,还是不上了吧,emmm。为什么吗说是检测我们的设计问题呢?因为VIP出租车必须使用继承的方式完成。这就要求我们原来的出租车必须要有较为完整的设计,才能接下来的操作。对于我来说,论证我的可追踪出租车可能有些强词夺理,但是我还是将要求实现了。具体实现的方法是这样的。
我并没有设计ArrayList将所有的出租车数据作为对象来存起来。而是将他们全都先直接输出到文件,作为中间存储的介质,最后在需要的时候再将他们输出出来。看起来这样写也是挺好的,但是这样并不是老师的初衷。为什么需要将数据作为对象来存储起来呢?我之后在想这样一个问题,在我们的程序中,重要的是对对象的处理,只有将数据作为对象存储器来,才能真正意义上的实现迭代器。但是我们将数据输出到文件,需要的时候再拿出来,通常是工业上常常采用的方式。这样的好处是随随时都可以访问大量的数据,还可以通过其他程序进行访问。但是指导书的要求是要实现迭代器,这样却无法实现。
关于出租车的继承,我们需要满足LSP原则,首先什么是JSP原则呢?
所有引用基类的地方必须能透明地使用其子类的对象。
通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
那么我是怎样满足的呢?
我们在设计的过程中使用了继承。Super_Taxi类是从Taxi类继承来的。那么他们之间是怎样满足要求的呢?首先,我再创建100个出租车时候是使用的是,Taxi类创建。这样的一个数组,但是接下来
for(int i=1; i<=100; i++) {
if(i<=30) {
cars[i] = new Super_Taxi(i,map_GUI.gui,guiinfo);
map_GUI.gui.SetTaxiType(i, 1);
}
else {
cars[i] = new Taxi(i,map_GUI.gui,guiinfo);
map_GUI.gui.SetTaxiType(i, 0);
}
}
在这里,我们将前30辆出租车向下转型,也就是初始化为Super_Taxi类,然后还是这样的数组,我将这个数组的数据结构传到了其它的对象中作为其他的对象的属性。但是属性都是以Taxi定义的,这也就是说明了,只要父类出现的地方,子类就可以出现。所以不会有错。然后就是我们在传的接口是用的是Taxi,但是我们可以看到的是,出租车Super_Taxi和Taxi里面,我的子类就是重写了父类的一些方法的。虽然接口是Taxi,但是我们最终调用的还是Super_taxi里面的方法。这也就满足了多态的向下转型。综上所诉,这个继承的是满足LSP原则的。
总结:对不起,我可能写不好JSF了
首先拿个自己的JSF出来试试水。
//Requires: str_in
//Modifies: find_num
//Effect:请求输入->判断是否合法 && 获取请求的信息
这是input()方法的JSF,对, 就是这样,三次作业都没改过。好好看着我这反面教材……。(人品是多么重要,(o゜▽゜)o☆ ),那么多JSF都是这么写,只被报了一个Crash。再次感谢第三次测我代码的那位好心人。
虽然我自己的代码JSF写的不是很好。但是我在阅读别人的代码的过程中还是好好阅读过他们的JSF的。结论是,同样看不懂,但是我没有报过任何一个规格bug,( o=^•ェ•)o ┏━┓
总结起来了,就是这个东西确实很有用,但是在OO这门课程中,体现的不是很明显,反而成为互测被针对的地方。感觉有点违背了当时发明JSF的人的初衷,希望在之后的作业里面,大家可以友好相处,和谐六系。但是JSF的书写有问题,不代表设计也有问题。同样,可以写不好JSF,但是不能采用一个让人无法理解的设计方案。总之呢?就是强调设计方案的重要性,为什么重要呢?这里引用老子的一句话“合抱之木,生于毫末,九层之台,起于累土,千里之行,始于足下”。只要每一步都设计好,一步一步的走下去,最终一定能够走向成功。共勉,*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。