OO第三单元总结

OO第三单元总结

​ 本单元我了解了JML在编程中的作用:JML语言可以用于约束方法的前置条件和和后置条件,但不约束方法的实现过程。通过这几周的学习,我能读懂基本的JML规格并编写代码实现这些规格。此外,在本单元我也意识到了提前规划结构对代码性能的重要性,以及“维护”的重大意义。

1架构设计

​ 这一单元,很自然的想法是继承每一个接口实现自己的类,这基本已经决定了这些类之间的关系。此外,JML规格中还给了一些“虚拟的”变量。这些变量只是用来方便描述,但每一个类的属性也完全可以参照这些变量。

​ 因此,要考虑的是,是否要为了实现一些特定的方法必须引入新的变量、方法或新建一些辅助的类,以及是否因为性能的考虑而改变一些“虚拟变量”的实现手段。

1.1关于“虚拟变量”的实现手段

​ 可以看到,JML中的数组都使用“[ ]”描述。但显然这样的结构是不便于查找的,因此这让我很自然的想到HashMap和HashSet。但这两种结构的缺点是无序,这就要看JML规格是怎么规定的了。在一些地方我们可以看到JML规格很明显地暗示我们顺序是不重要的。

	/*@ public normal_behavior
      @ requires !hasPerson(person);
      @ assignable people;
      @ ensures (\forall Person p; \old(hasPerson(p)); hasPerson(p));
      @ ensures \old(people.length) == people.length - 1;
      @ ensures hasPerson(person);
      @*/
    public void addPerson(/*@ non_null @*/Person person);

​ 这里JML规定在addPerson之后,只要保证之前有的人现在还有就行,那这里显然我们就可以用Hash结构了。

​ 有的地方JML规定了顺序,比如一个人接受消息的时候,最后的消息会在最前面,那就用ArrayList。

​ 当然也不是没有例外,在queryBlockSum的描述看似规定了顺序,但通过数学推导或从名字上猜测,这一个方法是要返回“block”的个数,这个个数实际是跟人的顺序没关系的,因此可以放心用Hash结构。(从这一点上也可以看到,JML虽然足够严谨规范,但有些时候是真不说人话,就比如那个最小生成树,不想一会我是真看不出来他在说什么)

​ 此外,为了提高性能,一组数据也未必只用一个容器存储。在不同的时候可调用不同的容器。

​ 比如对于Person中的acquitance和value,我是这样实现的。

	private ArrayList<MyPerson> acquaintance;
    private ArrayList<Integer> value;
    private HashMap<MyPerson, Integer> acqMap;

​ 在查询两个人之间的value时,可以用HashMap。而在计算最小生成树时,我已经将熟人和他的value按照value从小到大的顺序分别一一对应地排在了两个ArrayList中,这可以提高Prim算法的性能。只是要注意这几个容器必须随时保持同步,在改变一个容器时,也要改变另几个就好。

1.2 关于额外的类,方法和属性,以及浅谈“维护”。

​ 上面已经提到queryBlockSum方法是返回block的个数,且这个方法是和isCircle,queryValue紧密相连的。在参考了并查集算法之后,我设计了一个类BLocks来辅助实现这三个方法,这个类中有如下这个容器。

private static HashMap<Integer, HashMap<Integer, MyPerson>> blocks = new HashMap<>();

​ 每一个block中的人之间都是互相连通的,不同block的人之间不连通。上面这个容器中的第一个Integer是一个人的Id,代表着一个block中某一个人的Id,这个block中的所与人都被标记为这个Id(具体来说在MyPerson这个类里,有一个mark属性,用来记录这个Id),代表着这些人属于这个团体,内层的这个HashMap自然就包含着block中每一个人的Id和这个人。

​ 这样当新加入一个人时,这个人以自己的Id为自己的标记,并新建一个block。当两个人之间产生关系时,需要判断这两个人是否已经在同一个block中,如果不是,那就需要将这两个block融合为一个,并更改人数较少的block的所有人的标记为另一个block的标记。

​ 于是,在新加一个人或者新加一个关系时,需要额外的维护Blocks内部的内容。但是在调用isCircle等方法时,复杂度是常数级的。后面最小生成树算法和最短路径算法我也都是在Blocks的基础上实现的,节省了这两个算法初始化阶段的时间消耗。

​ 这就是通过在建立阶段花费时间(维护)来提高应用(调用方法)时的性能。类似的,qgvs指令也应该采用维护,qgav应该先算出平均值,之后就不要在一遍遍地调用平均值函数了。

​ 但也不是所有地方都需要强制维护,像最小生成树算法,我也只是通过BLocks提前知道了这个最小生成树有哪些人,如果想在每次添加关系的时候都维护一颗最小生成树,我还没有很好地方法,这样维护反而会花费很大的时间。

1.3 整体概况

​ 在上面的基础上介绍每一个类中比较重要的属性(不重要的忽略了)

MyGroup:

private int valueSum;  //用于qgvs,上文已经提到
private HashSet<MyPerson> people;  //方便查询

MyPerson:

private ArrayList<MyPerson> acquaintance;
private ArrayList<Integer> value;
private HashMap<MyPerson, Integer> acqMap;  //如上文

MyNetWork:

private HashMap<Integer, MyGroup> groups;
private HashSet<Integer> groupSet;   //之所以要对group同时设置一个Set和一个Map,是因为可以用Set遍历Map,减少遍历时的初始化时间。
private HashMap<Integer, Integer> emojiList;
private HashSet<Integer> emojiIdSet;  //同上面的group,同时设置Set和Map

测试数据

​ 这一单元的测试主要是靠和别人对拍,是我的同学提供了随机生成数据的代码。

​ 我自己只写了几组数据,都是大量的最下生成树指令之类的,用于查看自己代码的时间是否达标。

bug,hack

​ 在第一次中,我对BLocks的维护有漏洞。在新建一个person的时候就将这个人放入了blocks的容器中,但实际上这个人可能会触发异常,应该在addPerson时再将它加入BLocks中。

​ 在第二次中,qgvs指令因没有采用维护而超时,因此采取了上文的方法。

​ 在第三次中没有别发现bug。

​ 我的hack策略基本上是通过大量数据看别人的性能达不达标。也成功地hack在前两次作业hack了别人。

Network的扩展

​ 可以新建一个Produce类,类中存储产品类别(produceId),产品编号(produceSpecialId),产品价格(money)等以及其他可能需要的信息等等

​ 广告可以通过继承Message类实现,可能包含产品种类,广告内容等

​ Advertiser,Producer和Customer可以通过继承Person实现,Producer应该额外存储其生产的产品类别等,并有一个容器用来表示自己已经生产好的产品。Advertiser可以接受Customer的消息并向Producer放出消息,且可以发出其产品类别相关的广告,还要作为产品的中转站负责得到生产出来的产品并发放给顾客。Customer中应有一个数组用来存放购买来的物品。

​ 此外,为了让顾客可以发出订单也为了推销员可以向生产商发出订单,还应该建立一种新的Message类型,也就是“购买消息”,OrderMessage。

​ 综上,大致需要以下方法:

public void createProduce(int produceId, int money);  // 研发了一个ID为produceID,价格为money的产品。
public void makeAdvertisement(String text, int productId,...);  //制作了一条广告,除了一条信息应有的基本属性外,还应有广告内容,以及这个广告推广的产品的ID
public void postAdvertisement(int messageId);  //发布一条广告
public void	makeOrderMessage(int produceId,...);//表明顾客下单了一个产品
public void sendOrderToAdvertiser(int messageId); //customer将订单发送给advertiser
public void sendOrderToProducer(int messageId);	//advertiser将订单发送给producer
public void makeProduce(int producerId,int id, int produceSpecialId); //生产产品
public void queryProduceNum(int producerId, int produceId);//查看当前还有多少产品
public void postProduceToCustomer(int produceId, int produceSpecialId, int customerId);//producer发送产品给customer

下面实现了几个重要的方法

/*@
  @requires hasProducer(producerId) && hasProduceId(produceId) && 
  @!hasProduceSpecialId(produceSpecialId);
  @ assignable getProducer(producerId).getproduces();
  @ensures getProducer(producerId).getproduces().contains(produceSpecialId)
  @ && hasProduceSpecialId(produceSpecialId) 
  @ && (\forall int i;\old(getProducer(producerId).getproduces().contains(i));
  @ getProducer(producerId).getproduces().contains(i))
  @ && getProducer(producerId).getproduces().length ==
  @ \old(getProducer(producerId).getproduces().length)+1 ;
  @*/
public void makeProduce(int producerId, int produceId, produceSpecialId);
/*@ 
  @ ensures \result == getProducer(producerId).getProduce(produceId).length;
  @*/
public /*@ pure @*/ int queryProduceNum(int producerId, int produceId);
/*@
  @requires hasProducer(producerId) && hasProduceId(produceId) && 
  @hasProduceSpecialId(produceSpecialId);
  @ assignable getProducer(producerId).getproduces();
  @ assignable getCustomer(customerId).getproduces();
  @ensures !getProducer(producerId).getproduces().contains(produceSpecialId)
  @ && (\forall int i;\old(getProducer(producerId).getproduces().contains(i));
  @ getProducer(producerId).getproduces().contains(i)==> i!=produceSpecialId)
  @ && getProducer(producerId).getproduces().length ==
  @ \old(getProducer(producerId).getproduces().length)-1 ;
  @ensures getCustomer(customerId).getproduces().contains(produceSpecialId)
  @ && (\forall int i;\old(getCustomer(customerId).getproduces().contains(i));
  @ getCustomer(customerId).getproduces().contains(i))
  @ && getCustomer(customerId).getproduces().length ==
  @ \old(getCustomer(customerId).getproduces().length)+1 ;
  @*/
public void postProduceToCustomer(int produceId, int produceSpecialId, int customerId);

学习体会

​ 在这个单元首先是学会了一种让代码“规范”的手段:JML。我以往写的代码都是小规模的,因此只需要自己明白就可以了。但是如果是大规模的代码,且需要许多人协同完成,那代码的规范就十分重要了。JML正式提供了这样一个规范手段。

​ 除此之外,我还有两个收获:可以通过在数据建立阶段提前处理的方式减少后期应用时的时间消耗;如果想提高性能,可以额外写辅助的类,方法或添加多重属性。

posted @ 2022-06-03 13:30  罗夏0324  阅读(26)  评论(0编辑  收藏  举报