软件构造Lab3小结

1 实验目标概述

本次实验覆盖课程第 2、3 章的内容,目标是编写具有可复用性和可维护性 的软件,主要使用以下软件构造技术:

  • 子类型、泛型、多态、重写、重载
  • 继承、委派、CRP
  • 语法驱动的编程、正则表达式
  • 设计模式

本次实验给定了多个具体应用,学生不是直接针对每个应用分别编程实现, 而是通过ADT 和泛型等抽象技术,开发一套可复用的ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使ADT有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。

2 实验环境配置

本次实验使用IntelliJ IDEA开发,使用Maven环境。

3 实验过程

3.1 待开发的三个应用场景

1 TrackGame
3 AtomStructure
5 SocialNetworkCircle
径赛场地赛程编排(TrackGame):在本实验中,我们将场地形状简化成圆形。圆形周围划分为等距的若干跑道,在短距离赛跑比赛中每个跑道容纳1名运动员(本实验 无需考虑长距离赛跑中运动员不区分跑道的情形)。场地中心点无物体,无需考虑各跑道的半径,将各运动员看作独立个体(彼此之间不存在关系),比赛过程中运动员不能切换跑道。 给定一组运动员,通过比赛方案编排算法,将他们分为n组,为每组内的每个运动员分配具体的跑道。从而,每一组运动员就形成了一个轨道系统。
原子结构模型(AtomStructure):一个原子由原子核和绕核运动的电子组成,原子的质量主要集中在由质子和中子构成的原子核上,核外分布着电子。在物理学家Rutherford于 1909年提出和Bohr于1913年改进的核结构模型中,原子可看作是以原子核为中心的多条电子轨道的结构。本实验中,将原子核作为整体看作中心点的物体,无需考虑其内部的质子和中子。无需考虑电子之间的关系。电子在轨道的分布遵循特定的原理,这里无需考虑电子的绝对位置(Heisenberg 的测不准原理),也无需考虑电子在轨道上的运动。但电子可以在不同轨道之间跃迁。
社交网络的好友分布(SocialNetworkCircle):在一个社交网络中, 用户a有一级好友、二级好友、…、n级好友,其中一级好友是指与a直接有社交关系的人,i级好友是指与 a 无直接的社交关系,但是与 a 的 第 i-1 级好友有直接社交关系的人。因此,若以a为中心点,则可将其 各级好友表示为多轨道的结构,第i条轨道上分布着a的i级好友,一个人只能出现在一条轨道上。
可以看出共性在于他们都符合轨道系统的结构。其中TrackGame没有中心物体,AtomStructure和SocialNetworkCircle都有唯一的中心物体。TrackGame每条轨道上只能有一个物体。只有SocialNetworkCircle需要处理对象之间的关系。

3.2 基于语法的图数据输入

3.3 面向复用的设计:CircularOrbit<L,E>

  • 定义泛型接口CircularOrbit<L,E>,作为轨道系统的顶层接口。
  • 其中,L为中心物体的类型,E为轨道物体的类型
  • 定义下列函数:
  • 迁移轨道物体至某条轨道void transit(E obj, Track t);
  • 在轨道上移动轨道物体至一定角度void move(E obj, double angle);
  • 添加一条轨道void addTrack(Track t);
  • 移除一条轨道void removeTrack(Track t);
  • 添加一个中心物体void addCentralObject(L obj);
  • 添加一个轨道物体 void addPhysicalObject(Track t, E obj);
  • 删除一个轨道物体void removePhysicalObject(E obj);
  • 清除所有轨道物体void clearPhysicalObject();
  • 添加一个关系void addRelation(Object a, Object b, double dist);
  • 删除一个关系void removeRelation(Object a, Object b);
  • 获取关系连接的目标物体,已经相应的边权Map<Object, Double> getRelationTargets(Object obj);
  • 获取关系连接的源物体,已经相应的边权Map<Object, Double> getRelationSources(Object obj);
  • 获取所有轨道,按半径从小到大排列List getTracks();
  • 获取所有轨道物体Set getPhysicalObjects();
  • 获取某条轨道上的所有轨道物体Set getPhysicalObjects(Track track);
  • 获取轨道物体所在的轨道Track getObjectTrack(E obj);
  • 获取物体所在的角度位置double getObjectAngle(E obj);
  • 获取所有中心物体的集合Set getCentralObjects();
  • 删除某个中心物体void removeCentralObject(L obj);

3.4 面向复用的设计:Track

定义一个不可变类型Track,仅需一个成员字段radius表示半径:

private final double radius;

重写equals,hashCode和toString方法
equals仅需比较半径是否相等:

if (this == obj) return true;
if (!(obj instanceof Track)) return false;
Track other = (Track) obj;
return radius == other.radius;

hashCode使用成员变量radius的hashCode即可:

return Double.hashCode(radius);

3.5 面向复用的设计:Person

由于SocialNetworkCircle 中的CentralUser和Friend类型字段完全相同,抽象出Person类型作为他们的共同基类。
定义不可变类型Person表示人,它是CentralUser和Friend的基类型。
Person需要三个字段,分别表示姓名、年龄、性别。

private final String name;//姓名
private final int age;//年龄
private final char gender;//性别

重写hashCode,用三个字段的hashCode做计算的结果作为Person的hashCode

return name.hashCode() * 1331 + Integer.hashCode(age) * 13 + Character.hashCode(gender);

重写equals,三个字段同时相等时,Person对象才相等

if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return name.equals(other.name) && age == other.age && gender == other.gender;

3.6 面向复用的设计:L

3.6.1 AtomCore

定义不可变类型AtomCore表示原子核,它是AtomStructure的中心物体。
仅需一个字段elementName表示元素符号

private final String elementName;

重写equals,hashCode和toString方法
equals仅需比较元素符号是否相等:

return (obj instanceof AtomCore) && elementName.equals(((AtomCore) obj).elementName);

hashCode使用成员变量radius的elementName即可:

return elementName.hashCode();

3.6.2 CentralUser

定义不可变类型CentralUser表示中心用户,它是SocialNetworkCircle的中心物体。
CentralUser继承Person,无需额外属性。
重写equals方法,仅需验证对象类型并调用基类equals即可:

return (obj instanceof CentralUser) && super.equals(obj);

无需重写hashCode,直接从Person继承即可

3.7 面向复用的设计:PhysicalObject

定义接口PhysicalObject表示轨道物体,所有CircularOrbit派生类中使用的轨道物体类型需实现此接口。

3.7.1 Athlete

定义不可变类型Athelete表示朋友,它是TrackGame的中心物体。
定义final字段name, number, nation, age, bestScore分别代表姓名、编号、年龄、国籍、年龄、历史最好分数。

private final String name, number, nation;
private final int age;
private final double bestScore;

重写equals和hashCode:
equals中各个字段都相等即Athlete对象相等

if (this == obj) return true;
if (!(obj instanceof Athlete)) return false;
Athlete other = (Athlete) obj;
return name.equals(other.name) && number.equals(other.number) && nation.equals(other.nation) && age == other.age && bestScore == other.bestScore;

hashCode中使用各字段的hashCode的多项式运算结果作为Athlete的hashCode

int val = 0;
val = val * 31 + name.hashCode();
val = val * 31 + number.hashCode();
val = val * 31 + nation.hashCode();
val = val * 31 + Integer.hashCode(age);
val = val * 31 + Double.hashCode(bestScore);
return val;

3.7.2 Electron

定义不可变类型Electron表示电子,它是AtomStructure的中心物体。
考虑到Electron没有特殊属性,人为加入一个全局唯一id属性区分不同的电子。

private static final Object counterLock = new Object();
private static int counter;
private final int id;

counter为全局计数器,每构造一个电子就加一。counterLock为同步锁,确保线程安全性。
构造函数中,将counter的值赋给id,并使counter自增1

synchronized (counterLock) {
    id = counter;
    counter++;
}

重写hashCode,使用id的hashCode
重写equals,判断类型和id是否相等即可

3.7.3 Friend

定义不可变类型Friend表示朋友,它是SocialNetworkCircle的中心物体。
Friend继承Person,无需额外属性。
重写equals方法,仅需验证对象类型并调用基类equals即可
无需重写hashCode,直接从Person继承即可

3.8 可复用API设计

定义类型CircularOrbitAPIs,为辅助函数类,用于计算熵值等。
实现函数getObjectDistributionEntropy,用于计算轨道系统的熵值:
public static <L, E> double getObjectDistributionEntropy(CircularOrbit<L, E> c)
内部实现为遍历每一个Track,根据track上物体的数量和物体的总数量套用公式计算即可:
熵公式

实现函数getLogicalDistance,用于计算轨道系统两物体间逻辑距离
实现函数getPhysicalDistance,用于计算轨道系统两物体间物理距离

3.9 图的可视化:第三方API的复用

使用java swing进行GUI设计和多轨道系统的可视化展示。

3.9.1 初始界面

初始界面
点击“切换为此应用”按钮,弹出文件选择对话框,可选择相应的文件初始化多轨道系统:
切换为此应用

3.9.2 TrackGame可视化及操作

TrackGame
选择方案二:
选择方案二

3.10 设计模式应用

3.10.1 工厂模式

构造 Track、PhysicalObject 等对象时使用了工厂模式。
工厂模式
定义PhysicalObjectFactory作为轨道物体工厂类的接口,具体的轨道物体工厂类均实现此接口。
接口函数PhysicalObject fromString(String s);
用途是从字符串中创建轨道物体对象。
AthleteFactory、ElectronFactory、FriendFactory均实现此接口,并在重写的fromString函数中利用正则表达式解析字符串,并构造具体的轨道物体对象。

3.10.2 Iterator模式

使用 Iterator 设计模式,设计迭代器,客户端可在遍历 CircularOrbit 对象中的各 PhysicalObject 对象时使用,遍历次序为:从内部轨道逐步向外、同一轨道上的物体按照其角度从小到大的次序(若不考虑绝对位置,则随机遍历)。
使CircularOrbit<L, E>接口继承Iterable接口

public interface CircularOrbit<L, E> extends Iterable<E> 

在ConcreteCircularOrbit<L,E>中实现Iterator iterator();方法:

3.10.3 strategy 设计模式

在 TrackGame 应用中,使用 strategy 设计模式实现多种不同的比赛方案编排策略,并可容易的切换不同的算法。
strategy
先将比赛编排分为两步:

  • 给运动员分为不同的组别
  • 给每一组组内的运动员排次序分配跑道
    定义GroupStrategy接口作为分组策略类的接口,所有给TrackGame提供分组策略的类均需实现此接口。
    接口中定义doGroup函数将运动员分组:
List<List<Athlete>> doGroup(List<Athlete> athletes);

NumberAscendGroup和NumberDescendGroup实现了GroupStrategy接口,分别提供按编号升序和降序进行分组的策略。

定义SortStrategy接口作为组内排次序策略类的接口,所有给TrackGame提供组内排次序策略的类均需实现此接口。
接口中定义doSort函数将组内运动员排序:

void doSort(List<Athlete> athleteGroup);

NumberAscendSort和NumberDescendSort实现了SortStrategy接口,分别提供按编号升序和降序进行组内排次序的策略。

TrackGame类中,定义organizeGame方法进行比赛编排,接收一个GroupStrategy参数和一个SortStrategy参数作为比赛编排的分组策略和组内排次序策略:

public void organizeGame(GroupStrategy groupStrategy, SortStrategy sortStrategy)

3.11 应用设计与开发

3.11.1 TrackGame

定义可变类型TrackGame,继承ConcreteCircularOrbit<Object, Athlete>,表示径赛运动。
定义字段athletes存储所有运动员,gameGroup存储比赛编排结果,groupSelected存储当前选中的比赛组别。

private final Set<Athlete> athletes = new HashSet<>();
private List<List<Athlete>> gameGroup = null;
private int groupSelected;

重写transit、addCentralObject、removeCentralObject函数,均直接抛出UnsupportedOperationException异常,因为TrackGame不支持这些操作。
重写removePhysicalObject函数,从athletes中移除运动员

super.removePhysicalObject(obj);
athletes.remove(obj);

实现organizeGame方法,实现对比赛的编排:

public void organizeGame(GroupStrategy groupStrategy, SortStrategy sortStrategy)

3.11.2 AtomStructure

定义可变类型AtomStructure,继承ConcreteCircularOrbit<AtomCore, Electron>,表示原子结构。
实现函数SeparateElectron,实现对各层电子的均匀排布:
遍历所有Track,遍历track上的所有电子,使用move函数移动

for (Electron electron : electrons) {
    move(electron, Math.PI * 2 * i / electrons.size());
    ++i;
}

重写addPhysicalObject函数,自动均匀隔开电子

3.11.3 SocialNetworkCircle

定义可变类型SocialNetworkCircle,继承ConcreteCircularOrbit<CentralUser, Friend>,表示社交网络结构。
由于关系为双向,重写addRelation进行双向连边

super.addRelation(a, b, dist);
super.addRelation(b, a, dist);

由于关系为双向,重写removeRelation进行双向去边

super.removeRelation(a, b);
super.removeRelation(b, a);

定义函数updatePositions,用于根据题目要求,以距离CentralUser的远近更新Friend所处的轨道:

public void updatePositions()

3.12 应对应用面临的新变化

3.12.1 TrackGame

定义类型TrackGameNew,作为新的变化后的TrackGame类型,继承TrackGame进行扩展

public class TrackGameNew extends TrackGame 

实现很简单,仅需调用父类的构造函数,将一个轨道最多四人的限制传入即可

super(4, game, numOfTracks, athletes);

定义类型TrackGameFactoryNew,作为新的工厂类,继承TrackGameFactory进行扩展
重写create函数,为重用代码,先调用父类的create函数创建TrackGame对象,再利用TrackGame对象的信息构造TrackGameNew对象:

TrackGame trackGame = super.create(s);
return new TrackGameNew(trackGame.getGame(), trackGame.getTracks().size(), trackGame.getAthletes());

3.12.2 AtomStructure

定义类型AtomicStructureNew,作为新的变化后的AtomicStructure类型,继承AtomicStructure进行扩展

public class AtomStructureNew extends AtomStructure

需要扩展两个新成员numOfNeutron, numOfProton,分别是原子核中质子和中子的数量

定义类型AtomicStructureFactoryNew,作为新的工厂类,继承AtomicStructureFactory进行扩展
重写create函数,为重用代码,先调用父类的create函数创建AtomicStructure对象,再利用AtomicStructure对象的信息构造AtomicStructureNew对象

3.12.3 SocialNetworkCircle

定义类型SocialNetworkCircleNew,作为新的变化后的SocialNetworkCircle类型,继承SocialNetworkCircle进行扩展
public class SocialNetworkCircleNew extends SocialNetworkCircle
需重写addRelation和removeRelation函数,将双向加边更改为单向加边

@Override
public void addRelation(Object a, Object b, double dist) {
    addOneWayRelation(a, b, dist);
}

定义类型SocialNetworkCircleFactoryNew,作为新的工厂类,继承SocialNetworkCircleFactory进行扩展
重写create函数,由于SocialNetworkCircle为双向边,构造完成后失去原来的边的方向信息,因此无法重用代码,参考SocialNetworkCircleFactory::create重新实现即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值