oo电梯作业小结
虽然只进行了三次作业,但它们成功地把我的思维从面向过程拉到了面向对象上边。
我的三次作业有一个共性,就是完成地十分匆忙。在周末除了一丢丢公事本有大量时间可以利用,但无奈自己懒癌发作不想写代码,导致我在周一到周三这几天特别匆忙,DDL临近的时候,我更愿意的是保险的办法——面向过程式的编程。因此我的第一次作业中,一个函数里边8层if环环相扣,中间间隔着若干for循环,各种flag遍布全文……它最后还是过了,虽然还是漏了一些要点被扣了分。到了第二次和第三次,一个函数占据大半程序的事情也发生了,不管是调试还是修改,都给我造成了不小的麻烦。在这个周末,我决定乘着这个空闲的时间,将第三次作业的代码全部推倒,重新写一遍(以下我也将主要介绍第二三次作业,第一次的作业过于面向过程,基本是java版的c代码,就不贴了)。
也许是慢慢地熟悉了这种思维方式,在重新写的过程中我感受到自己有了异常清晰的思路,每一个类,每一个方法的功能都像一块块零件在脑子里拼接……好了,不废话,直接摆一张类图(第二次以及第三次作业):

类的说明:
请求类(Request):
它储存了一条指令的所有信息,它也提供了将输入的字符串转化的方法(构造方法);
电梯类(Elevator):
它储存了电梯的运行状态,同时提供了通过指令运行电梯、判断同质、判断捎带的方法;
队列类(Queue):
它提供了请求队列供调度类使用,同时它也提供了队列的基本操作方法;
调度类(Schedule_I/ Schedule_I):
它掌控电梯的调度,I为第二次作业的调度方式,II为第三次作业的调度方式。
电梯运行接口(FunctionOfElevator):
电梯运行的基本方法;
主类(Main):
程序入口。
我的设计思路:
读取指令-检查错误-加入队列这些都是预处理工作,不用说,我单单来说执行过程:
1. 我们每一次从队列中取出一条指令,判断完它是否可用(是不是前面指令的同质指令以及前面是否被捎带过);
2. 然后遍历它(第一条主指令)后边的每一条指令,同质(相对于当前主指令)指令给它做一个标记;
3. 非同质的我们检查它是否能被捎带,能被捎带的话加入一个数组中去;
4. 当我们将主指令所有能捎带的指令都加完后,在其中找一条最靠近主指令的(能够最先被捎带的),如果没有这样的指令转到7;
5. 然后执行它或者是主指令(取决于谁能先被执行);
6. 将未执行的指令作为主指令,转到2;
7. 执行主指令;
8. 继续读取下一条指令。
依靠这样的算法,我们就能在每一次电梯状态更新后找到下一个捎带目标,从而实现捎带。
分析与评价:
交作业的代码因为时间紧张,没有进行很好的设计,导致我的调度类很庞大,很臃肿,一个调度类里边的执行函数,长度达到了整个代码长度的一半以上,而后来我也是发现自己对不同类的功能做的不够完善,这不仅使调度类“很忙乱”,也让其他类的封装性变的很差,有两个类基本是对外透明的。
因此,修改代码时,我在封装性上考虑较多,我将判断同质和判断是否可捎带的方法放在电梯类(Elevator)中,对于命令正确性的判断,我分为语法、数值、时间判断,实现方法分别在调度类(Schedule),请求类(Request),队列类(Queue)实现,减少了对象内属性的传递,也减少了调度类中Operation方法的代码行数,变得更加易读,贴一下调度的代码:
1 Schedule_I(){ 2 int amount=0; 3 Request req; 4 Scanner sc=new Scanner(System.in); 5 while(true) { 6 String s=sc.nextLine(); 7 s=s.replaceAll(" ",""); 8 amount++; 9 if(amount>100||s.equals("RUN"))break; 10 Pattern p1 = Pattern.compile("\\(FR,\\+?(0){0,20}(([1-9])|(10)),((DOWN)|(UP)),\\+?[0-9]{1,20}\\)"); 11 Pattern p2 = Pattern.compile("\\(ER,\\+?(0){0,20}(([1-9])|(10)),\\+?[0-9]{1,10}\\)"); 12 Matcher m1 = p1.matcher(s); 13 Matcher m2 = p2.matcher(s); 14 if(!m1.matches() && !m2.matches()) {error(s);continue;}//正则匹配判断 15 req=new Request(s); 16 if(req.check()==false){error(s);continue;}//数值检测 17 if(queue.checkT(req)==false){error(s);continue;}//时间检测 18 queue.push(req); 19 } 20 sc.close(); 21 }
1 void operation(){ 2 Request mainreq,req1,req2,tmp; 3 while(queue.isEmpty()==false) { 4 mainreq=queue.pop();//取出指令 5 if(mainreq.unique()==false) {//判断是否被标记为重复 6 repetition(mainreq.getStr()); 7 continue; 8 } 9 if(mainreq.avilable()==false) {//判断是否被标记为不可用 10 continue; 11 } 12 ele.setState(mainreq);//根据主指令设置电梯状态 13 14 req1=mainreq; 15 while(req1!=null) { 16 while(true) { 17 req2=queue.search();//找到最初主指令下一条指令 18 if(req2==null)break; 19 if(!(req2.unique()==true && req2.avilable()==true))continue;//判断是否可用 20 if(!ele.isUnique(req1, req2)) {//判断是否同质 21 req2.unique(false); 22 }else { 23 ele.addPickableReq(req1, req2);//判断是否可捎带并将可捎带指令加入数组 24 } 25 } 26 req2=ele.getPickableReq();//取出数组中离主指令最近的一条指令 27 if(req2==null)break; 28 if((ele.getState().equals("UP") && req2.getFloor()>req1.getFloor()) ||//判断主指令与捎带指令谁先被执行 29 (ele.getState().equals("DOWN") && req2.getFloor()<req1.getFloor())) { 30 tmp=req1; 31 req1=req2; 32 req2=tmp; 33 } 34 ele.move(req2);//执行能够先执行的指令 35 req2.avilable(false); 36 } 37 ele.move(req1);//执行主指令 38 } 39 }
各种方法的调用也让调试变的简单,哪里有问题,只需要看看具体方法里十几行代码就行。
贴一张度量的图片:
一点点感想:
在刚开始使用面向对象的思维编程时,我遇到的最大困难是我不知道我此时写的类它应该有什么功能,我写的方法和外部应该交流那些数据,每一个方法应该放在哪一个类中才能尽可能不破坏封装性,每一个方法应该完成百分之多少的工作……此时我也感受到两种思维方式的差别:使用面向过程编程时,你只要考虑这一步做完程序会得到什么结果,靠着这个结果我们如何完成下边的运算,然后按部就班完成设计;面向对象时你要考虑的是这一个类它该有什么功能它才能完成整体的要求,它和外界需要交流些什么,当类设计结束后你还要考虑整体如何来使用这些实例化后的对象,如何“组装”这些“组件”来达到我们的要求。总之面向对象使得原本线性的过程变成了模块化、可组装的过程。
这三次作业给我最深的感受就是使用面向对象的设计时,一定要在设计时尽可能地想清楚每一个类它们能做什么,它们在整体中的位置,它和其它类之间有什么样的数据交流……只有明确结构和目标,才能在写代码时得心应手。
浙公网安备 33010602011771号