[软件构造]实验回顾:Lab3
实验回顾:Lab3
实验目标:Reusability and Maintainability oriented Software Construction
本次实验目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 常见的OO设计模式
⚫ 语法驱动的编程、正则表达式
⚫ 基于状态的编程
⚫ API设计、API复用
本次实验给定了五个具体应用(径赛方案编排、太阳系行星模拟、原子结构可视化、个人移动App生态系统、个人社交系统),学生不是直接针对五个应用分别编程实现,而是通过ADT和泛型等抽象技术,开发一套可复用的ADT及其实现,充分考虑这些应用之间的相似性和差异性,使ADT有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。
第一部分:应用场景的选定与分析
对于要开发的三个应用场景(行星运动模拟,原子结构模型、社交网络好友分布),分析他们之间的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
共性:都可以总结为一个有中心的多轨道系统,可以进行增加删除轨道、增加删除轨道物体、添加中心物体、添加任意两个物体间的关系、在轨道上移动物体、将某个物体迁移到某个轨道的操作。
个性:
1.行星运动模拟:中心是一个恒星;每个轨道上必须有且只有一个行星;行星有自己的名称、大小、质量、运动速度等属性;行星不能改变轨道;需要计算恒星和行星、行星和行星之间的距离;相邻轨道的半径之差不能小于两颗相应行星的半径之和。
2.原子结构模型:中心是一个原子核,每个轨道上均匀分布着相同的电子;电子可以跃迁。
3.社交网络好友分布:中心是一个中心用户,第n轨道上放有与中心用户具有n级好友关系的用户,若某个用户与中心用户没有社交关系,则不出现在图中;任意两个用户之间都可存在亲密度关系。
这样,在CircularOrbit接口中,就能确定哪些方法是他们共有的,就可以统一设计。
第二部分:ConcreteCircularOrbit的设计
在ConcreteCircularOrbit中,需要实现CircularOrbit中给定的所有基本功能:
⚫ 创建一个空的CircularOrbit对象
⚫ 增加一条轨道、去除一条轨道
⚫ 增加中心点物体
⚫ 向特定轨道上增加一个物体(不考虑物理位置)
⚫ 增加中心点物体和一个轨道物体之间的关系
⚫ 增加两个轨道物体之间的关系
⚫ 从外部文件读取数据构造轨道系统对象(在每个具体应用的构造方法体现)
⚫ 将object从当前所在轨道迁移到轨道t
⚫ 将object从当前位置移动到新的sitha角度所对应的位置
为了实现功能方便,还增加了新方法:移除轨道上的物体。
第三部分:三个具体实现的设计
对于每个具体实现,针对对他们各自的特点,设计或者重写特定的方法。
比如,为了使用、测试,分别编写两个构造方法(以StellarSystem为例):
public StellarSystem(Stellar s, List<Planet> planets){
this.addCenter(s);
for(Planet p: planets) {
this.addTrack(p.getTrack());
this.addObjectOnTrack(p, p.getTrack());
}
checkRep2();
}
public StellarSystem(String fileName) throws IOException {
File file = new File(fileName);
FileReader reader = new FileReader(file);
BufferedReader br = new BufferedReader(reader);
List<String> lines = new ArrayList<>();
String nextLine = new String();
while((nextLine = br.readLine()) != null)
lines.add(nextLine);
String[] temp = lines.get(0).split("<");
String[] temp1 = temp[1].split(">");
String[] strs = temp1[0].split(",");
this.addCenter(new Stellar(strs[0].trim(), Double.valueOf(strs[1].toString()), Double.valueOf(strs[2].toString())));
for(int i = 1; i < lines.size(); i++) {
temp = lines.get(i).split("<");
temp1 = temp[1].split(">");
strs = temp1[0].split(",");
Planet p = new Planet(strs[0].trim(), strs[1].trim(), strs[2].trim(), Double.valueOf(strs[3].toString()), Double.valueOf(strs[4].toString()),
Double.valueOf(strs[5].toString()), strs[6].trim(), Double.valueOf(strs[7].toString()));
this.addTrack(p.getTrack());
this.addObjectOnTrack(p, p.getTrack());
}
checkRep2();
br.close();
}
对于某个特定的场景,有些方法不需要太严格的检查就能执行,比如行星系统中添加行星:
@Override
public boolean addObjectOnTrack(MyObject object, Track t) {
List<MyObject> temp = this.getObjects().get(this.getTracks().indexOf(t));
temp.add(object);
checkRep();
return true;
}
还有某个场景特定的方法,比如行星系统中计算行星的位置:
public Position calcPosition(Planet p, double time) {
double d = 1.0;
if(p.getDirection() == "CCW")
d = -1.0;
double r = p.getTrack().getRadius();
double angle = p.getAngle() + d * p.getSpeed() / r * time * 180.0 / Math.PI;
while(angle > 360.0)
angle -= 360.0;
double x = r * Math.cos(angle * Math.PI / 180);
double y = r * Math.sin(angle * Math.PI / 180);
System.out.printf("Planet: %s Angle: %.3f X: %.3f Y: %.3f\n",p.getName(), angle, x, y);
checkRep2();
return new Position(x, y);
}
对于SocialNetworkCircle,我在Lab3中编写的轨道物体间的关系都是双向存储的:这是因为,312change部分要求改成有向图的形式,所以我在这里做了些准备,到时候注释掉一些代码就行了。虽然说这里与Lab4里边的要求稍有冲突,但是在编写代码的第一时间能多看看后边的要求,为后边的内容稍微考虑考虑,还是能培养人的大局观念的。
if(strs[1].trim().equals(p.getName())) {
this.addObjectOnTrack(p, t);
this.addRelation(pe, p, new Relation<MyObject>(pe, p, Double.valueOf(strs[2].trim().toString())));
this.addRelation(p, pe, new Relation<MyObject>(p, pe, Double.valueOf(strs[2].trim().toString())));
unaddedPeople.remove(p);
newAddedPeople.add(p);
b = true;
}
然而在我的SocialNetworkCircle这里,删除一条轨道的话,要想不出问题,就要删掉轨道上的所有物体;而删除物体又要删除这个物体身上所有的关系。所以在删除轨道的方法中,要调用多次删除物体的方法;同样,删除物体的时候也要调用多次删除关系的方法。
if(Math.abs(t1.getRadius()-t.getRadius())<0.001){
List<MyObject> l = this.getObjects().get(this.getTracks().indexOf(t1));
for(int i = 0; i < l.size(); i++) {
MyObject o = l.get(i);
this.removeObjectOnTrack(o, t1);
}
this.getObjects().remove(this.getTracks().indexOf(t1));
this.getTracks().remove(t1);
checkRep();
System.out.println("Track removed successfully.");
return true;
}
第四部分:几个底层ADT的设计
这里主要是用private和final防止表示泄露,没什么其他的重点。
第五部分:可复用API设计
这里设计几个实用的方法,计算物理距离、逻辑距离、熵值等,只要编程基础扎实,就不会出现问题。
第六部分:可视化界面的设计
这里主要是现学现用,自己上网查找可视化的方法,然后用到自己的程序里面。我在这里花费了大量的时间,搜集了很多资料去学习,最后发现,有很多都是没有什么用的。所以,我希望我们的课程能多提供一些实用的Java基本手册,这样省去我们大量的时间精力,还避免跟着网上文章里边的做法养成不好的习惯。
第七部分:设计模式应用&主程序设计
由于对设计模式的理解有误,我查了课件和书籍,用了很长的时间去修改自己的代码,去应用这些设计模式。实际上,这个实验并不能体现出这些设计模式的很强的优势来,我个人认为,只有Lab6中的设计模式比较合适,这里用到,有的时候不太方便。
但是设计模式还是很重要的,比如,能够让不一样的东西在一个地方生产出来(工厂模式),根据指令生产实例。
主程序设计就是菜单,switch,case,没什么可讲的。
第八部分:312change
312change作为一个分支,主要目的是考量已经设计好的ADT适应变化的能力。比如,行星轨道变成 符合实际的椭圆形轨道;原子核需要表达为多个质子和多个中子,即处于中心点的物体可以是多个物体构成的集合;为社交关系增加方向性。
在我的实现中,这些对于原程序的改动都不大。对于行星轨道,再加一个变量,表示为纵横比,修改横坐标计算方法就可以了;表示原子核的类,加两个整形变量,作为质子数和中子数即可;社交关系的方向性,上边已经说过,直接注释掉部分代码即可。
下一篇: 关于小程序研发的文章推荐