OO第三单元总结

2022北航OO第三单元总结

本单元作业模拟实现了一个社交网络系统,可通过各类输入指令实现此社交网络中成员、消息及关系等各类信息数据的增删改查。本单元作业让我一方面从阅读了解到熟悉并学会使用JML规格化语言,另一方面进一步领悟复杂算法的优化方法及其必要性。

第一次作业

第一次作业需要实现主类、NetWork类、Person类及Group类和6个异常类,从而实现社交关系的模拟和查询。

算法分析

第一次作业中复杂度较高的指令是qci及qbs,涉及的方法分别是isCiclequeryBlockSum

  • isCircle可理解为检查两个结点是否联通,可通过判断两个结点的根节点是否相同来实现
    • 新增容器记录结点的根节点,采用了并查集并用递归实现对根节点的查找,同时进行路径压缩优化
  • queryBlockSum可理解为检查当前图中有几个连通分量,可通过空间换时间实现
    • 新增变量blockSum记录当前连通分量的数量;初始为0,并在addPersonaddRelation时进行维护

bug分析

  • 本次作业由于对并查集理解不到位,导致在addRelation时对根节点的合并有误,从而导致强测的qci和qbs出现问题
    • MyNework类中新增level数组记录各结点的深度,合并根节点时将深度更大结点的根节点作为共同根节点

第二次作业

第二次作业新增Message类和两个与之相关的异常类,同时新增Network类中的若干方法

算法分析

第二次作业中新增的复杂度较高的指令是qgvs和qlc,涉及的方法分别是queryGroupValueSumqueryLeastConnection

  • queryGroupValueSum即查询指定group中的value和,同样可采用空间换时间实现
    • 在Network类中设置valueSum变量,并在addPerson,delPersonaddRelation时进行维护
  • queryLeastConnection可以理解为寻找给定id的结点所在连通分量的最小生成树,并返回权值之和,可以用堆优化的prim算法实现
    • 采用了Java中的优先队列容器PriorityQueue存储各结点的distance(新建PerosnDist类作为容器存储对象),优先队列可省去一层遍历最小距离的循环;其余即实现prim算法部分

bug分析

  • 本次作业由于MyNetwork类中的getPerson方法采用了一层循环实现,prim算法又在循环中调用了getPerson方法,导致有强测有两个点的qlc出现CTLE
    • 修改HashMap的查询直接使用containskey方法
    • 各类中较简单的方法在第一次作业实现时没有考虑时间问题,第二次作业完成时又忽略了这些简单方法存在的问题而直接调用。对于各方法都应尽可能控制复杂度并在写代码的时候时刻注意复杂度是否过高

第三次作业

第三次作业新增继承自MessageEmojiMessageRedEnvelopeMessageNoticeMessage以及与之相关的6个异常类。同时Network类中新增若干方法

算法分析

第三次作业中新增的复杂度较高的指令是sim,涉及的方法是sendIndirectMessage

  • sendIndirectMessage可以理解为寻找给定的两节点间的最短距离,并返回距离值,可采用堆优化的迪杰斯特拉算法
    • 与第二次作业的qlc类似,同样采用优先队列容器PriorityQueue存储各结点distance,只是在算法上采用迪杰斯特拉算法

bug分析

  • 中测时有一个点一直WA且无法看到数据,最终发现是在addMessage抛出EmojiIdNotFoundException异常时传入了messageId而非emojiId
    • 由于实现的方法很多且细节很繁琐,找这种细节bug的过程就像大海捞针一样。在第一遍写代码时就应该多阅读几遍JML,在完成一个方法,尤其是较复杂的方法后就要及时检查一遍

UML架构图

  • 结构图中主要体现了各类的数据成员(为体现使用的容器)和为实现规格或降低复杂度而新增的方法(即不在JML规格中给出的方法)
  • 由于类较多且实现完全按照给出的规格,架构图中没有体现异常类
classDiagram class MainClass MainClass: +main(String[] args)void
classDiagram class MyNetwork Network<|..MyNetwork MyNetwork: -HashMap<Integer, Person> people MyNetwork: -HashMap<Integer, Group> groups MyNetwork: -HashMap<Integer, Message> messages MyNetwork: -HashMap<Integer, Integer> roots MyNetwork: -HashMap<Integer, Integer> level MyNetwork: -HashMap<Integer, Integer> emojiHeatList MyNetwork: -int blockSum MyNetwork: -findRoot(int id)int MyNetwork: -prim(int id, HashMap<Integer, Person> circlePeople)int MyNetwork: -dijkstra(Person person1, Person person2)int class MyGroup Group<|..MyGroup MyGroup: -int id MyGroup: -HashMap<Integer, Person> people MyGroup: -int valueSum MyGroup: +addLink(Person person1, Person person2, int value)void class MyPerson Person<|..MyPerson MyPerson: -int id MyPerson: -String name MyPerson: -int age MyPerson: -int money MyPerson: -int socialValue MyPerson: -HashMap<Integer, Person> acquaintance MyPerson: -HashMap<Integer, Integer> value MyPerson: -ArrayList<Message> messages MyPerson: +addAcquaintance(Person person)void MyPerson: +addValue(Person person, int num)void
classDiagram class MyNoticeMessage NoticeMessage<|..MyNoticeMessage MyMessage<|--MyNoticeMessage MyNoticeMessage: -String string class MyEmojiMessage EmojiMessage<|..MyEmojiMessage MyMessage<|--MyEmojiMessage MyEmojiMessage: -int emojiId class MyRedEnvelopeMessage MyMessage<|--MyRedEnvelopeMessage RedEnvelopeMessage<|..MyRedEnvelopeMessage MyRedEnvelopeMessage: -int money class MyMessage Message<|..MyMessage MyMessage: -int id MyMessage: -int socialValue MyMessage: -int type MyMessage: -Person person1 MyMessage: -Person person2 MyMessage: -Group group
  • 本次社交网络系统的大致结构可类比为一个无向图:Network类是一个图,其中含有若干结点(Person类),同时包含若干组别(Group类),组别包含图中的部分结点以及自身的特定属性。各结点含有自己的各种特定属性,各结点之间可能存在关系及权值,并可进行消息的发送。以上关系及各类属性均可增删改查。
  • 关于容器的选择,由于对象与id的一一对应性以及操作中频繁出现的循环查找,大多采用了HasMap存储数据;同时为方便满足JML规格给出方法的返回值要求,Person类中的Message采用ArrayList存储。

测试

  • 本单元作业还是沿用了前几个单元的测试方法,在构造数据的基础上与同学进行对拍。除了大规模数据对拍,在此之前先单独分组测试分别每个指令对应方法的正确性。首先测试方法的基本功能,主要考虑可能出现的若干情况、抛出的异常是否正确、方法实现的细节是否遗漏或误写;其次测试复杂指令的边缘情况等是否有误,尽量做到全面考虑。

Network扩展及JML规格

大致设计结构

  • 新增Producer类,Advertiser类,Comsumer类继承自Person类;新增ProductMessage类继承自Message
  • Network
    • 新增属性:总产品队列,总广告消息队列,总已购队列;
    • 新增方法:生产产品,发送广告,关注广告,购买产品
      • 生产产品:将新建的ProductMessage对象加入总产品队列和poducerId对应的个人产品队列,并设置对象的producerId;可能出现poducerId未找到或productId重复异常
      • 发送广告:将产品加入总广告队列和advertiserId对应的个人广告队列,并设置对象的advertiserId;可能出现productId或advertiserId未找到异常(productId需在总产品队列中)
      • 关注广告:将广告加入consumerId对应的个人喜爱队列;可能出现productId或consumerId未找到异常(productId需在总广告队列中)
      • 购买产品:先设置产品的consumerId,再根据productId得到对应的产品对象,从而得到产品销售路径;将对象从总产品队列、总广告队列删除,加入总购买队列;根据得到的销售路径(producerId,advertiserId,consumerId),将对象从个人产品队列、个人广告队列、个人喜爱队列删除,加入个人已购队列;可能出现productId或consumerId未找到异常(productId需在个人喜爱队列中)
  • Producer
    • 属性:个人产品消息队列
  • Advertiser
    • 属性:个人广告消息队列
  • Comsumer
    • 属性:个人喜爱队列,个人已购队列
  • ProductMessage
    • 属性:价格;producerId,advertiserId,consumerId(用于记录商品销售路径)
classDiagram Message<|--ProductMessage ProductMessage: -int price ProductMessage: -int producerId ProductMessage: -int advertiserId ProductMessage: -int consumerId
classDiagram class Producer class Advertiser class Consumer Person<|--Producer Person<|--Advertiser Person<|--Consumer Producer: -HashMap<Integer, ProductMessage> selfProducts Advertiser: -HashMap<Integer, ProductMessage> selfAdvertisements Consumer: -HashMap<Integer, ProductMessage> selfPreferList Consumer: -HashMap<Integer, ProductMessage> selfBuyList
classDiagram class Network Network: -HashMap<Integer, ProductMessage> products Network: -HashMap<Integer, ProductMessage> advertisements Network: -HashMap<Integer, ProductMessage> buyList Network: +produce(int producerId, int productId, int price)void Network: +sendAdvertisement(int advertiserId, int productId)void Network: +getAdvertisement(int consumerId, int productId)void Network: +buy(int consumerId, int productId)void

JML规格

//生产产品
/*@ public normal_behavior
      @ requires contains(producerId) && !containsMessage(productId);
      @ assignable products;
      @ ensures products.length == \old(products.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(products.length);
      @          (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
      @ ensures (\exists int i; 0 <= i && i < products.length; products[i].getId == productId && products[i].getPrice == price && products[i].getProducerId == producerId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(producerId);
      @ signals (EqualMessageIdException e) containsMessage(productId);
      @*/
public void produce(int producerId, int productId, int price) throws PersonIdNotFoundException, EqualMessageIdException;

//发送广告
/*@ public normal_behavior
      @ requires contains(advertiserId) && containsMessage(productId);
      @ assignable advertisements;
      @ ensures advertisements.length == \old(advertisements.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(advertisements.length);
      @          (\exists int j; 0 <= j && j < advertisements.length; advertisements[j] == (\old(advertisements[i]))));
      @ ensures (\exists int i; 0 <= i && i < advertisements.length; advertisements[i].getId == productId && advertisements[i].getAdvertiserId == advertiserId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(advertiserId);
      @ signals (MessageIdNotFoundException e) !containsMessage(productId);
      @*/
public void sendAdvertisement(int advertiserId, int productId) throws PersonIdNotFoundException, MessageIdNotFoundException;

//关注广告
/*@ public normal_behavior
      @ requires contains(consumerId) && containsMessage(productId);
      @ assignable getPerson(consumerId).getselfPreferList;
      @ ensures getPerson(consumerId).getselfPreferList.length == \old(getPerson(consumerId).getselfPreferList.length) + 1;
      @ ensures (\forall int i; 0 <= i && i < \old(getPerson(consumerId).getselfPreferList.length);
      @          (\exists int j; 0 <= j && j < getPerson(consumerId).getselfPreferList.length; getPerson(consumerId).getselfPreferList[j] == (\old(getPerson(consumerId).getselfPreferList[i]))));
      @ ensures (\exists int i; 0 <= i && i < getPerson(consumerId).getselfPreferList.length; getPerson(consumerId).getselfPreferList[i].getId == productId && getPerson(consumerId).getselfPreferList[i].getConsumerId == consumerId);
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(consumerId);
      @ signals (MessageIdNotFoundException e) !containsMessage(productId);
      @*/
public void getAdvertisement(int consumerId, int productId) throws PersonIdNotFoundException, MessageIdNotFoundException;

心得体会

  • 本单元第一次接触JML语言和规格化设计,通过JML手册阅读学习和三次作业的迭代完成,对JML语言的理解进一步加深,尤其是对大段且复杂的JML语言的阅读理解能力大大提高,同时也让我理解了规格化设计的重要性;按照JML语言实现代码的一大要点是对细节的注重,需要及时的反复的检查和多做测试;同时我也感受到降低方法复杂度至关重要,而读懂JML语言的要求,能顾用自己的话去更形象的描述、理解规格的要求,是实现复杂方法及复杂方法的前提条件。
  • 本单元作业也让我意识到了自己在算法方面的不足,一方面是没有系统的学习过算法,另一方面是数据结构的知识忘得七七八八,需要进一步巩固和提升。
  • 本单元作业深刻的体会到了测试的重要性。虽然某些功能看起来并不难且实现起来也并不复杂,但也不能忽视测试的重要性,尤其是JML规则细节繁琐时,很容易出现疏漏,而一个小错误造成的后果是不可估量的。
posted @ 2022-06-01 10:42  realgyyyyy  阅读(58)  评论(1编辑  收藏  举报