3.3 面向复用的设计:CircularOrbit<L,E>· 1
3.10.4 PersonalAppEcosystem· 2
3.11.4 PersonalAppEcosystem· 3
本次实验覆盖课程第 3、5、6 章的内容,目标是编写具有可复用性和可维护
性的软件,主要使用以下软件构造技术:
⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 常见的 OO 设计模式
⚫ 语法驱动的编程、正则表达式
⚫ 基于状态的编程
⚫ API 设计、API 复用
本次实验给定了五个具体应用(径赛方案编排、太阳系行星模拟、原子结构
可视化、个人移动 App 生态系统、个人社交系统),学生不是直接针对五个应用
分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其
实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可
复用性)和更容易面向各种变化(可维护性)。
简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。
建立远程仓库链接:
$ git init命令初始化
$ git remote add origin <url> 添加远程仓库
$ git add.添加文件到仓库
$ git commit –m “”提交
$ git push上传到远程仓库
在这里给出你的GitHub Lab3仓库的URL地址(Lab3-学号)。
https://github.com/ComputerScienceHIT/Lab3-1170300724.git
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
首先请列出你要完成的具体应用场景(至少3个,1和2中选一,3必选,4和5中选一,鼓励完成更多的应用场景)。
- TrackGame
- AtomStructer
- SocialNetWorkCicle
分析你所选定的多个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
相同点:
都有多个轨道,轨道上有一些物体,轨道上的物体属于一个类,要实现增加轨道,删除轨道,在某个轨道上增加物体,在某个轨道上删除物体,从文件中按照一定规则读取信息,并建立轨道系统。
不同点:
TrackGame:
①需要分成多个组,每个组都是一个轨道系统
②每组中每个轨道上都最多只能有一个Athlete
③运动员位置的分配有两种方案且两种方案可以随时切换
④没有中心点
AtomStructer
①每个电子之间都没有区别
SocialNetWorkCicle
①轨道物体之间、轨道物体与中心点之间可能存在双向的具有权值的边
②在关系确定的情况下,每个物体所在的轨道都是确定不可改变的
③第一轨道上的Friend都具有扩散度这一属性
④物体之间具有逻辑距离
TrackGame:
String regex_1="(Athlete ::= <)([\\w,.\\d]+)(>)";
String regex_2="(Game ::= )([,.\\d]+)";
String regex_3="(NumOfTracks ::= )([\\w,.\\d]+)";
AtomStructer:
String regex_1="(NumberOfElectron ::= )([\\d/;]+)";
String regex_2="(ElementName ::= )((\\w){1,2})";
String regex_3="(NumberOfTracks ::= )(\\d)";
SocialNetWorkClcle:
String regex_1="(CentralUser ::= <)([\\w\\d,.]+)(>)";
String regex_2="(Friend ::= <)([\\w\\d,.\\s]+)(>)";
String regex_3="(SocialTie ::= <)([\\w\\d,.\\s]+)(>)";
利用正则表达式
regex1匹配中心点用户信息,<>中信息的形式是<姓名,年龄,性别>,姓名是Word,由多个字母构成,有大写,也有小写,年龄是正整数,姓名是字符串,是“M”““”“F”之一。
regex2匹配关系,关系由两个人名及其亲密度组成,SocialTie ::= <用户 1 姓名,用户 2 姓名,社交亲密度>, 亲密度是范围在(0,1]内的小数,最多 3 位小数位,用户 1 和用户 2 可以是 CentralUser 或 Friend,由于文件中存在空格,于是匹配的时候加入“\\s”。
regex3匹配朋友信息,朋友信息和中心用户信息形式一致,但<>中具有多余的空格,匹配时先把空格读入,在录入信息时将空格删去
读入后还要进行合法性检查,保证不会出现错误的显示
-
- 面向复用的设计:CircularOrbit<L,E>
public interface CircularOrbit<L,E> {
* get tracks map
public Map<Track, List<E>> getTracks();
* get center L
public L getCenter();
* get center relation List
public Map<E, Double> getCenterRelation();
* get relation map E and E
public Map<E, Map<E, Double>> getRelation();
* public getPosition map
public Map<E, Position> getPositions();
* Create an empty circularOrbit.
public static <L,E> CircularOrbit<L,E> empty() {
return new ConcreteCircularOrbit<L, E>();
}
* add some tracks to this game
public boolean addTrack(Track track);
* delete a track of this system
public boolean removeTrack(Track track);
* add a centraobject
public boolean addCentra(L center);
* add object to track
public boolean addObjectToTrack(E e,Track track);
* add relation between centra and an object
public boolean addCentraRelation(E e,double d);
* add relation between tow object on track
public boolean addRelation(E e1,E e2,double d);
* create an Orbit from reading file
public boolean Creat(String Fielname);
* change the track
public boolean transit(Track t1,Track t2);
* move an object on a track
public boolean move(E object, double sitha);
* next
public void next();
* remove a object on track
public boolean removeObjectOnTrack(E e,Track t);
-
- 面向复用的设计:Track
Track只具有2个属性:id和半径
private int r;
Object pobject;
private String name;
public Track(String name,int r) {
this.r=r;
this.name=name;
}
public String getName() {
return this.name;
}
public int getR() {
return this.r;
}
public boolean addObject(L newObject)
{
if(isExist(newObject))
return false;
pobject.add(newObject);
return true;
}
public boolean remove(L newObject)
{
//int size=pobject.size();
//for(int i=0;i<size;i++)
{
//if(pobject.get(i).equals(newObject))
{
//pobject.remove(i);
return true;
}
}
return false;
}
public boolean isExist(L obj)
{
//int size=pobject.size();
//for(int i=0;i<size;i++)
{
//if(pobject.get(i).equals(obj))
return true;
}
return false;
}
public L get(L obj)
{
//for(int i=0;i<pobject.size();i++)
{
//if(pobject.get(i).equals(obj))
// return pobject.get(i);
}
System.out.println("not exist");
return null;}
@Override
public boolean equals(Object obj) {
if (obj instanceof Track) {
Track t= (Track) obj;
return r==t.getR();
}
return false;
}
@Override
public int hashCode() {
return this.r;
}
centralObject类作为父类,只有名字name一个属性
所有L均为centralObject, TrackGame中其实没有中心物体,作为泛型的L,为防止空指针错误,设计一个EmptyCentral表示空中心,EmptyCentral的名字一定是empty,可以通过名字判断中心是否为空。
public class ConcreteCircularOrbit<L,E> implements CircularOrbit<L, E>
public static <L,E> CircularOrbit<L,E> empty()
-
- 面向复用的设计:PhysicalObject
为了实现可复用的PhysicalObject,设计了一个ADT,其他类都继承自这个ADT
public abstract class PhysicalObject {
String name;
public String getName() {
return this.name;
}
/*public PhysicalObject(String name)
{
this.name=name;
}*/
public PhysicalObject(String name) {
super();
this.name = name;
}
public class Electron extends PhysicalObject{
private final boolean isDifferent=false;
private int number;
public boolean isDifferent() {
return isDifferent;
}
public Electron(String name) {
super(name);
}
public String getName()
{
return this.name;
}
public int getNumber()
{
return this.number;
}
@Override
public String toString() {
return this.getName();
}
@Override
public boolean equals(Object obj)
{
if(!(obj instanceof Electron))
return false;
Electron f=(Electron)obj;
return f.number==this.number;
}
}
public class Person {
private String name;
private int age;
private String sex;
private final boolean isDifferent=true;
public boolean isDifferent() {
return isDifferent;
}
public Person(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
@Override
public String toString() {
return this.name;
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Person))
return false;
Person person= (Person) obj;
return name.equalsIgnoreCase(person.getName().trim());
}
@Override
public int hashCode()
{
int sum=0;
for(int i=0;i<name.length();i++)
{
sum+=(int)name.charAt(i);
}
return sum;
}
}
public class Player extends PhysicalObject{
private String nation;
private int number;
private int age;
private double bestScore;
private final boolean isDifferent=true;
public boolean isDifferent() {
return isDifferent;
}
public Player(String name,int number, String nation, int age, double bestS) {
super(name);
this.number = number;
this.nation = nation;
this.age = age;
this.bestScore = bestS;
}
public int getNumber() {
return this.number;
}
public String getNation() {
return this.nation;
}
public int getAge() {
return this.age;
}
public double getBestScore() {
return this.bestScore;
}
@Override
public String toString() {
return this.getName()+" "+this.bestScore;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Player) {
Player person= (Player) obj;
return this.getName().equalsIgnoreCase(person.getName().trim());
}
return false;
}
@Override
public int hashCode() {
return this.age;
}
-
- 可复用API设计
计算熵值公式:-∑x(i)ln(x(i)),x(i)=number(i)/sum,number(i)第i个轨道上物体的数量,sum是所有轨道上物体的总数。
public double getObjectDistributionEntropy(CircularOrbit<L,E> c) {
double entropy=0;
double sum=0;
Map<Track, List<E>> OTmap = new HashMap<>();
for(Track t:OTmap.keySet()) {
sum+= OTmap.get(t).size();
}
sum=0;
for(Track k:c.getTracks().keySet()) {
sum+=c.getTracks().get(k).size();
}
for(Track t:OTmap.keySet()) {
double pi = OTmap.get(t).size()/sum;
if(pi==0)
continue;
entropy += -pi*Math.log(pi)/Math.log(2);
}
entropy=0;
for(Track k:c.getTracks().keySet()) {
double y=(double)c.getTracks().get(k).size();
double x=y/(double)sum;
entropy+=(-x*Math.log(x));
}
return entropy;
}
计算逻辑距离
每次取出队首元素并将和该节点有关系的元素入队
被放入的元素相应的Integer设置为父结点Integer+1
返回e2对应的Integer,若一直找不到,则返回-1
public int getLogicalDistance (CircularOrbit<L,E> c, E e1, E e2) {
Map<E, Map<E, Double>> relationMap=c.getRelation();
List<E> gragh=new ArrayList<E>();
for(E k:relationMap.keySet()) {
gragh.add(k);
}
int visit[]=new int[gragh.size()];
Map<E, Integer> map=new HashMap<E, Integer>();
for(int i=0;i<gragh.size();i++) {
map.put(gragh.get(i), i);
}
if(e1.equals(e2)) {
return 0;
}
List<E> que=new ArrayList<E>();
que.add(e1);
while(!que.isEmpty()) {
if(que.get(0).equals(e2)) {
return visit[map.get(que.get(0))];
}
else {
if(relationMap.get(que.get(0))==null) {
que.remove(0);
continue;
}
for(E k:relationMap.get(que.get(0)).keySet()) {
if(visit[map.get(k)]==0) {
visit[map.get(k)]=visit[map.get(que.get(0))]+1;
que.add(k);
}
}
que.remove(0);
}
}
return -1;
}
计算物理距离
利用余弦定理可以求得e1,e2的距离。
public double getPhysicalDistance (CircularOrbit<L,E> c, E e1, E e2) {
double result;
Map<E, Position> positionMap=c.getPositions();
int r1=positionMap.get(e1).getR();
int r2=positionMap.get(e2).getR();
double bearing1=positionMap.get(e1).getBearing();
double bearing2=positionMap.get(e2).getBearing();
result=r1*r1+r2*r2-2*r1*r2*Math.cos(bearing2-bearing1);
result=Math.sqrt(result);
return result;
}
求取轨道系统之间的差异
public class TrackDifference<L,E> {
int id;
int number;
boolean flag=false;
List<E> listA=new ArrayList<E>();
List<E> listB=new ArrayList<E>();
public TrackDifference(List<E> track1,List<E> track2,int id){
this.id=id;
int n1=track1.size();
int n2=track2.size();
this.number=n1-n2;
List<E> lista=new ArrayList<E>();
for(int i=0;i<track1.size();i++) {
if(track1.get(i) instanceof Electron) {
flag=true;
}
lista.add(track1.get(i));
}
List<E> listb=new ArrayList<E>();
for(int i=0;i<track2.size();i++) {
if(track2.get(i) instanceof Electron) {
flag=true;
}
listb.add(track2.get(i));
}
lista.removeAll(track2);
listb.removeAll(track1);
this.listA=lista;
this.listB=listb;
}
-
- 图的可视化:第三方API的复用
class MyPanel<L,E> extends JPanel{
private static final long serialVersionUID = 1L;
CircularOrbit<L, E> c;
public MyPanel(CircularOrbit<L, E> c) {
super();
this.c = c;
}
public void paint(Graphics g){
super.paint(g);
//g.drawString(c.getCenter().toString(), 1000, 500);
g.drawString(c.getCenter().toString(), 1000, 500);
Map<E,Double> centralRelation=c.getCenterRelation();
Map<E, Map<E, Double>> relationMap=c.getRelation();
Map<E,Position> positionMap=c.getPositions();
for(E k:centralRelation.keySet()) {
if(positionMap.get(k)==null) {
continue;
}
int r=positionMap.get(k).getR();
double bearing=positionMap.get(k).getBearing();
g.drawLine(1000,500,(int)(1000.0+50*r*Math.sin(bearing)), (int)(500.0+50*r*Math.cos(bearing)));
}
for(E k:relationMap.keySet()) {
for(E j:relationMap.get(k).keySet()) {
int r1=positionMap.get(k).getR();
double bearing1=positionMap.get(k).getBearing();
int r2=positionMap.get(j).getR();
double bearing2=positionMap.get(j).getBearing();
g.drawLine((int)(1000.0+50*r1*Math.sin(bearing1)), (int)(500.0+50*r1*Math.cos(bearing1)),(int)(1000.0+50*r2*Math.sin(bearing2)), (int)(500.0+50*r2*Math.cos(bearing2)));
}
}
for(Track k:c.getTracks().keySet()) {
int r=k.getR();
g.drawOval(1000-50*r, 500-50*r, 100*r, 100*r);
double b=35.0;
for(int j=0;j<c.getTracks().get(k).size();j++) {
g.drawString(c.getTracks().get(k).get(j).toString(), (int)(1000.0+50*r*Math.sin((Math.PI/180.0)*b*j)), (int)(500.0+50*r*Math.cos((Math.PI/180.0)*b*j)));
}
}
}
根据c画出图形,根据轨道的半径画同心圆,根据轨道中存的position在每个物体相应的位置标注,遍历关系Map,若两个人有关系,获取两个人的位置,在两个位置间连线。
策略模式
定义了strategy类,TrackGameStrategy1和TrackGameStrategy2继承自Strategy类,Strategy:
public List<TrackGame> strategy=new ArrayList<TrackGame>();
public TrackGame trackgame;
//Strategy和TrackGame的交互,初始时,TrackGame,create()读取文件,构造出一个存放运动员的List,TrackGameStrategy1和TrackGameStrategy2利用该List对运动员进行一定处理,并构建TrackGame,加入到strategy,形成不同的方案
public class TrackGame extends ConcreteCircularOrbit<EmptyCentral, Player>{
private int Gametype;
private int NumOfTracks;
private List<Player> players=new ArrayList<Player>();
public int getGametype() {
return Gametype;
}
public void setGametype(int gametype) {
Gametype = gametype;
}
public int getNumOfTracks() {
return NumOfTracks;
}
public void setNumOfTracks(int numOfTracks) {
NumOfTracks = numOfTracks;
}
public void putout() {
for(Player k:players) {
System.out.println(k.toString());
}
}
public int getRestNumber() {
return this.players.size();
}
public int nowGame=0;//当前组编号,用于GUI显示
TrackGameStrategy1:
public TrackGameStrategy1(TrackGame trackgame)//传入TrackGame,获取存放Player的List
public void init()//每次从List中取出若干个Player,构建TrackGame,加入到strategy中
public class TrackGameStrategy1 extends Strategy{
public TrackGameStrategy1(TrackGame trackgame) {
super();
this.trackgame = trackgame;
}
public void init() {
int now=1;
while(trackgame.getRestNumber()>0) {
System.out.println("第"+now+"组:");
trackgame.next();
strategy.add(trackgame.clone(trackgame));
trackgame.outPutOnTrack();
now++;
}
}
}
TrackGameStrategy2:
public TrackGameStrategy2(TrackGame trackGame)//传入TrackGame,获取Player的List
public void sortList()//对Player的List进行排序
public void init()//按成绩从差到好编排,每次取出若干个Player,成绩好的放在内轨道,成绩差的放在外轨道。
public class TrackGameStrategy2 extends Strategy{
public void sortList(){
trackgame.getPlayers().sort(new Comparator<Player>() {
@Override
public int compare(Player o1, Player o2) {
if(o1.getBestScore()>o2.getBestScore()) {
return -1;
}
return 1;
}
});
trackgame.getPlayers().forEach(System.out::println);
}
public TrackGameStrategy2(TrackGame trackGame) {
this.trackgame=trackGame;
}
public void init() {
int now=1;
while(trackgame.getRestNumber()>0) {
System.out.println("第"+now+"组:");
trackgame.next2();
strategy.add(trackgame.clone(trackgame));
trackgame.outPutOnTrack();
now++;
}
}
public static void main(String[] args) {
TrackGame trackgame=new TrackGame();
trackgame.Creat("TrackGame.txt");
TrackGameStrategy2 trackGameStrategy2=new TrackGameStrategy2(trackgame);
trackGameStrategy2.sortList();
trackGameStrategy2.init();
}
}
利用上述设计和实现的ADT,实现手册里要求的各项功能。
以下各小节,只需保留和完成你所选定的应用即可。
GUI可视化。
读取文件并建立轨道系统
增加轨道
删除轨道
增加物体
删除物体
计算熵值
编排不同比赛方案
改变运动员所在组和轨道
电子跃迁
public boolean transit(Track t1, Track t2) {
Electron electron=null;
for(Track k:this.getTracks().keySet()) {
if(k.getR()==t1.getR()) {
if(this.getTracks().get(k).size()>0) {
electron=this.getTracks().get(k).get(0);
this.removeObjectOnTrack(electron, t1);
this.addObjectToTrack(electron, t2);
return true;
}
}
}
return false;
}
计算逻辑距离
public int centralDistance(Person e) {
if(this.getCenterRelation().containsKey(e)) {
return 1;
}
CircularOrbitAPIs<Person, Person> circularOrbitAPIs=new CircularOrbitAPIs<Person, Person>();
int min=Integer.MAX_VALUE;
for(Person j:this.getCenterRelation().keySet()) {
if(circularOrbitAPIs.getLogicalDistance(this, j, e)<min
&&circularOrbitAPIs.getLogicalDistance(this, j, e)!=(-1)) {
min=circularOrbitAPIs.getLogicalDistance(this, j, e);
}
}
if(min==Integer.MAX_VALUE) {
return -1;
}
else {
return min+1;
}
}
以下各小节,只需保留和完成你所选定的应用即可。
在选择完策略放置的时候,每个轨道放4个
在中心物体类nucleus中加入两个list,分别用来存放质子和中子
改变添加关系的方法,将原来的加双向关系改为只加单向关系,在添加边的时候不添加反向边即可
并且在用户输入的时候进行判断
-
- Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚312change分支和master分支所指向的位置。
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
4-19 | 22:00-23:00 | 完成CircularOrbit接口,完成ConcreteCircularOrbit | 完成 |
4-20 | 6:30-23:30 | 完善3个基础类并排掉BUG | 完成 |
4-21 | 6:30-23:30 | 写完通用GUI | 完成 |
4-27 | 6:30-23:30 | 写API和部分GUI | 完成 |
4-28 | 6:30-23:30 | 写完3个具体的GUI | 完成 |
5.1-5.5 | 全天 | 测试、排BUG、newChange | 完成 |
遇到的难点 | 解决途径 |
GUI写不明白一直在报奇怪的错误
| 尽可能去规避可能的错误,借用了室友的GUI进行基础设计 |
- 重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在五个不同的应用场景下使用,你是否体会到复用的好处?
复用会增加代码出错的几率,但是减少了很多代码量,增加了效率 - 重新思考Lab2中的问题:为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后的编程中坚持这么做?
可以让别人更快的理解我的代码,增加沟通能力,方便查错 - 之前你将别人提供的API用于自己的程序开发中,本次实验你尝试着开发给别人使用的API,是否能够体会到其中的难处和乐趣?
设计一个好的API的确不容易,首先,一个好的API应该可以应用于多种场景,而不同场景往往有不同的特性,怎样根据这些场景设计一个兼容的功能,具有很大挑战性。 - 在编程中使用设计模式,增加了很多类,但在复用和可维护性方面带来了收益。你如何看待设计模式?
设计模式能增加代码的逻辑性和和后期处理的能力,是非常必要的。 - 你之前在使用其他软件时,应该体会过输入各种命令向系统发出指令。本次实验你开发了一个解析器,使用语法和正则表达式去解析输入文件并据此构造对象。你对语法驱动编程有何感受?
这种方法实用性太差了,对公式的书写要求很高,出现一点错误就会导致整个出错 - Lab1和Lab2的大部分工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验是你完全从0开始进行ADT的设计并用OOP实现,经过三周之后,你感觉“设计ADT”的难度主要体现在哪些地方?你是如何克服的?
主要难在思考的全面性,要不然后期还要重新写或者增加内容。每次要考虑周全,知道自己到底要干什么,到底需要什么样的功能。 - 你在完成本实验时,是否有参考Lab4和Lab5的实验手册?若有,你如何在本次实验中同时去考虑后续两个实验的要求的?
没有看 - 关于本实验的工作量、难度、deadline。
工作量真的很大,真的写不完。 - 到目前为止你对《软件构造》课程的评价。
更加坚信了软件构造的重要性,它绝非向我们介绍某种语言,而是向我们传授一些软件开发所通用的方法,模式,策略,从而提升开发效率,在实验中逐渐理解了各种模式的优越性。