欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[软件构造]实验回顾:Lab3

程序员文章站 2024-02-08 19:02:58
...

实验目标: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适应变化的能力。比如,行星轨道变成 符合实际的椭圆形轨道;原子核需要表达为多个质子和多个中子,即处于中心点的物体可以是多个物体构成的集合;为社交关系增加方向性。
在我的实现中,这些对于原程序的改动都不大。对于行星轨道,再加一个变量,表示为纵横比,修改横坐标计算方法就可以了;表示原子核的类,加两个整形变量,作为质子数和中子数即可;社交关系的方向性,上边已经说过,直接注释掉部分代码即可。