本次blog,针对第4~6次大作业题目集进行总结,目的在于总结不足、得出经验教训以及做出未来规划。本次blog分为以下几个部分。
一、前言:
(1)第4次和第6次大作业难度较大,需要考虑的情况很复杂多样,而且对整体设计能力的要求很高,类间关系设计继承、依赖、关联等等,而且因为输入的数据很多,某一部分读取或者输入的代码只要位置有一点不同,结果差别很大(这个后面也会提到,很重要!!),这样的错误往往很难察觉。第6次题目还涉及到很多物理知识,如并联电路电阻的计算、串并联电路的模拟等等,相比第4、5次大作业,第6次大作业的应用性更强,和现实中的实例联系更密切,对设计者模拟一个复杂系统的要求自然更高。
(2)这三次题目集,无一例外都考察了多种类间关系的设计以及对系统的模拟能力,要设计出一个符合实际、简洁高效的模拟系统需要设计者具有很强的建模能力、阅读理解能力(需要模拟的信息都是以文字形式呈现,三次大作业的题面阅读量都很大,需要设计者精确捕捉到有用信息)。前几次题目集我较为忽视阅读理解能力这一点,其实这是相当重要的,如果从第一步阅读理解就出了问题,代码的功能必然会有缺陷(因为需求的提取有偏差或者有疏漏),相比代码的正确性和高效性、可复用性等,功能不完全是非常致命的。
二、设计与分析:
因为第4次大作业和第5、6次模拟对象不同,这里分为两部分讨论。
(1)
第4次相比第六次在逻辑层面的设计思路很简单,设计者只需要有基本的生活常识就能有大致思路,但第6次加入了物理学知识后让系统的设计必须符合客观事实,难度大大提高。
(2) 本次bolg重点对第6次大作业进行讲解。
以下是第6次的类图。
由类图可以看到,类间关系首先要符合题面的基本描述,有串联电路并联电路,并且要注意串并联的多重关系:串联电路中可以有并联电路,并联电路中必须有串联电路。而且设备必须要细分为控制电路和受控电路,两者要继承自不同的父类(两种电器的属性差别很大,控制电路的理论电压应该为0,电阻也为0)。
其他地方和第4次题目集大同小异,这里就不多赘述。
类图看着简单吗?也许还算简单(相比第4次),但这并不代表代码层面简单。
首先串联电路中有设备,设备必须要用List不能用普通数组,如下
点击查看代码
private ArrayList<Equippment> equ=new ArrayList<>();
public void add(Equippment equippment)
{
this.equ.add(equippment);
}
还有并联电路中的串联电路,如下
点击查看代码
private ArrayList<SeriesCircuit> serCircuit=new ArrayList<>();
public void add(SeriesCircuit circuit)
{
this.serCircuit.add(circuit);
}
点击查看代码
if(cnt==sercnt-1)
{
for(int i=1;i<=3;i++)
{
partserCircuit[i].setVoltage(0);
}
for(int i=1;i<=2;i++)
{
paraCircuit[1].getSerCircuit().get(i).setVoltage(0);
}
}
if(cnt!=sercnt-1)
{
double[] res=new double[20];
double sum1=0;
double rall = 0;
for(int i=1;i<=sercnt-1;i++)
{
if(partserCircuit[i].getVoltage()!=0)
{
for(int j=0;j<partserCircuit[i].getEqu().size();j++)
{
res[i]+=partserCircuit[i].getEqu().get(j).getResistance();
}
}
}
for(int i=1;i<res.length;i++)
{
if(res[i]!=0)
{
sum1+=1/res[i];
}
}
paraCircuit[1].setResistance(1/sum1);
for(int i=0;i<partserCircuit[sercnt].getEqu().size();i++)
{
rall+=partserCircuit[sercnt].getEqu().get(i).getResistance();
}
rall+=paraCircuit[1].getResistance();
for(int i=1;i<=sercnt-1;i++)
{
voltage=paraCircuit[1].getResistance()/rall*partserCircuit[sercnt].getVoltage();
for(int j=0;j<partserCircuit[i].getEqu().size();j++)
{
partserCircuit[i].getEqu().get(j).setVoltage(voltage);
}
}
for(int i=0;i<partserCircuit[sercnt].getEqu().size();i++)
{
voltage=partserCircuit[sercnt].getEqu().get(i).getResistance()/rall*partserCircuit[sercnt].getVoltage();
partserCircuit[sercnt].getEqu().get(i).setVoltage(voltage);
}
for(int i=1;i<=sercnt;i++)
{
if(partserCircuit[i].getVoltage()!=0)
{
for(int j=0;j<partserCircuit[i].getEqu().size();j++)
{
voltage=partserCircuit[i].getEqu().get(j).getVoltage();
if(partserCircuit[i].getEqu().get(j).getNamestr()=="B")
{
if(voltage>=0&&voltage<=9)
{
partserCircuit[i].getEqu().get(j).setBright(0);
}
else
{
bright=150/210*(voltage-10)+50;
partserCircuit[i].getEqu().get(j).setBright(bright);
}
}
if(partserCircuit[i].getEqu().get(j).getNamestr()=="R")
{
if(voltage==0)
{
partserCircuit[i].getEqu().get(j).setBright(0);
}
else partserCircuit[i].getEqu().get(j).setBright(180);
}
if(partserCircuit[i].getEqu().get(j).getNamestr()=="D")
{
if(voltage<80)
{
partserCircuit[i].getEqu().get(j).setFanSpeed(0);
}
else if(voltage>=80&&voltage<=150)
{
double fanspeed=280/70*(voltage-80)+80;
partserCircuit[i].getEqu().get(j).setFanSpeed(fanspeed);
}
else
{
partserCircuit[i].getEqu().get(j).setFanSpeed(360);
}
}
if(partserCircuit[i].getEqu().get(j).getNamestr()=="A")
{
if(voltage<80)
{
partserCircuit[i].getEqu().get(j).setFanSpeed(0);
}
else if(voltage>=80&&voltage<100)
{
partserCircuit[i].getEqu().get(j).setFanSpeed(80);
}
else if(voltage>=100&&voltage<120)
{
partserCircuit[i].getEqu().get(j).setFanSpeed(160);
}
else if(voltage>=120&&voltage<140)
{
partserCircuit[i].getEqu().get(j).setFanSpeed(260);
}
else
{
partserCircuit[i].getEqu().get(j).setFanSpeed(360);
}
}
}
}
}
}
不过还有一点很难,就是对输入信息的处理顺序要安排得当:比如控制设备中的开关,它的权重是非常高的,很多情况下开关需要首先去遍历,如果不单独提出来可能会出现开关明明断开了,但是分档调速器和连续调速器还能改变电路的电压(电压居然不为0),这是不符合实际情况的,如下单独对开关讨论,这一点在后面踩坑心得会细说。
点击查看代码
for(int i=0;i<operation.size();i++)
{
str=operation.get(i);
c=str.charAt(2);
num=String.valueOf(c);
number=Integer.parseInt(num);
if(str.charAt(1)=='K')
{
for(int j=1;j<=sercnt;j++)
{
for(int t=0;t<partserCircuit[j].getEqu().size();t++)
{
if(partserCircuit[j].getEqu().get(t).getNamestr()=="K"&&partserCircuit[j].getEqu().get(t).getName().equals(num))
{
if(partserCircuit[j].getEqu().get(t).getIsOpen()==1)
{
partserCircuit[j].getEqu().get(t).setIsOpen(0);
partserCircuit[j].getEqu().get(t).setVoltage(0);
}
else
{
partserCircuit[j].getEqu().get(t).setIsOpen(1);
partserCircuit[j].getEqu().get(t).setVoltage(220);
}
}
}
}
}
}
点击查看代码
for(int i=1;i<=sercnt;i++)
{
partserCircuit[i].setOrder();
for(int j=0;j<partserCircuit[i].getEqu().size();j++)
{
mainEqu.add(partserCircuit[i].getEqu().get(j));
}
}
//对设备按输出要求排序
for(int i=0;i<mainEqu.size();i++)
{
Collections.sort(mainEqu);
}
点击查看代码
{
for(int j=1;j<=sercnt;j++)
{
for(int t=0;t<partserCircuit[j].getEqu().size();t++)
{
**if(partserCircuit[j].getEqu().get(t).getNamestr()=="K"&&partserCircuit[j].getEqu().get(t).getName().equals(num))**
{
if(partserCircuit[j].getEqu().get(t).getIsOpen()==1)
{
partserCircuit[j].getEqu().get(t).setIsOpen(0);
partserCircuit[j].getEqu().get(t).setVoltage(0);
}
else
{
partserCircuit[j].getEqu().get(t).setIsOpen(1);
partserCircuit[j].getEqu().get(t).setVoltage(220);
}
}
}
}
}
(2)关键设备的提前遍历。对于开关这种直接决定电压是否为0的设备,一定要首先遍历,这样才能保证当电路电压为0时其他控制设备不会再改变电压。
点击查看代码
for(int i=1;i<=sercnt;i++)
{
for(int j=0;j<partserCircuit[i].getEqu().size();j++)
{
if(partserCircuit[i].getEqu().get(j).getNamestr()=="K")
{
if(partserCircuit[i].getEqu().get(j).getIsOpen()==1)
{
partserCircuit[i].setVoltage(220);
}
else if(partserCircuit[i].getEqu().get(j).getIsOpen()==0)
{
partserCircuit[i].setVoltage(0);
}
}
}
}
(3)编程的连贯性。当程序很复杂的时候,思路一定要连贯,能一次性写完(对于某个模块)就要一次性写完,思路断了再写的时候会不知道从何写起,对于系统思维的建立很不利。
(4)独立解决问题不代表闷头死磕。适当的时候可以询问同学,看看其他同学有什么经验和建议。有时候同学的一句提示可能就是你思路的盲点。遇到无论如何都想不通、写不出的地方不要死磕,换一种方法去解决,比如我在计算并联电路电阻时,如果只有一条通路,就把并联电路作为串联并入串联电路。
(5)关于代码复用性。我承认目前我的设计能力还不够强,代码复用性相对较差,所以在对代码迭代时,不要死磕式修改旧的代码!!!这点非常重要!!!必要的时候可以选择重写,找bug改bug是一间费时费力的事,其实很多时候改bug的时间大于重写的时间,比如第6次大作业的电路编号是从1开始数的,但是我写数组的时候失误从0开始了,这就导致很多地方要把0改成1,改了好久还是有bug(越界报错,说明有地方还没改过来),这种时候就要果断选择重写,改写花的时间大概率会比改半天bug的时间短,如下
点击查看代码
for(int i=0;i<operation.size();i++)
{
str=operation.get(i);
c=str.charAt(2);
num=String.valueOf(c);
number=Integer.parseInt(num);
if(str.charAt(1)=='F')
{
char d=str.charAt(3);
for(int j=1;j<=sercnt;j++)
{
for(int t=0;t<partserCircuit[j].getEqu().size();t++)
{
if(partserCircuit[j].getEqu().get(t).getNamestr()=="F"&&partserCircuit[j].getEqu().get(t).getNamestr().equals(num))
{
if(d=='+'&&partserCircuit[j].getEqu().get(t).getPosition()<3)
{
partserCircuit[j].getEqu().get(t).upPosition();
}
else if(d=='-'&&partserCircuit[j].getEqu().get(t).getPosition()>0)
{
partserCircuit[j].getEqu().get(t).downPosition();
}
}
}
}
}
if(str.charAt(1)=='L')
{
tmp2=str.split(":");
speed=Double.parseDouble(tmp2[1]);
for(int j=1;j<=sercnt;j++)
{
for(int t=0;t<partserCircuit[j].getEqu().size();t++)
{
if(partserCircuit[j].getEqu().get(t).getNamestr()=="L"&&partserCircuit[j].getEqu().get(t).getName().equals(num))
{
partserCircuit[j].getEqu().get(t).setSpeed(speed);
}
}
}
}
}
五、总结:
(1)这三次大作业,我最大的收获就是对一个物理系统的模拟、设计能力提升。对于一个物理系统,基础的设计知识比如SRP、MVC是不够用的,充分理解物理系统的原理是必要条件。程序不仅要符合逻辑,也要严格符合物理规律。
(2)纵观我第6次大作业的完成情况,确实不算理想,我认为不只是设计能力,debug能力也几乎同等重要。对于一个模拟电路系统的复杂程序,只是具备细心度是远远不够的,还需要熟练操控eclipse的debug功能,而且始终保持系统思维,要清楚这部分代码是负责电路系统哪个模块的(模块与模块之间往往互相影响,如果没有系统思维那么很容易出现改了一个bug又有很多个bug出现的情况发生)
(3)代码的整合精简。目前第6次大作业中不少循环中的内容重复冗杂,当输入内容迭代时运行起来效率会很低,所以在编写代码之前就要有意识地精简、合并代码。
(4)大胆设计,小心编程。一开始编码时,我因为一直瞻前顾后、担心设计出问题而耽误了不少时间,结果最后设计还是有不少瑕疵,所以我认为可能没有最好的设计、不要过度犹豫。但是大胆设计不意味着草率设计,草率设计没有意义甚至还会有副作用,我们每次遇到的现实问题都不可能相同,但是把握好基础的设计原则,即使大胆设计也不会太大的问题。
(5)经验的积累通过这几次大作业,尤其是第6次,我认为每次大作业是积累编程经验的最好途径,大量的编程经验还是要从实践中获取,这些经验往往是细致入微的、实用的,这种经验也可以是某一套系统、某一个模型、某一种算法。