OO第三单元总结
2022北航OO第三单元总结
本单元作业模拟实现了一个社交网络系统,可通过各类输入指令实现此社交网络中成员、消息及关系等各类信息数据的增删改查。本单元作业让我一方面从阅读了解到熟悉并学会使用JML规格化语言,另一方面进一步领悟复杂算法的优化方法及其必要性。
第一次作业
第一次作业需要实现主类、NetWork
类、Person
类及Group
类和6个异常类,从而实现社交关系的模拟和查询。
算法分析
第一次作业中复杂度较高的指令是qci及qbs,涉及的方法分别是isCicle
和queryBlockSum
isCircle
可理解为检查两个结点是否联通,可通过判断两个结点的根节点是否相同来实现- 新增容器记录结点的根节点,采用了并查集并用递归实现对根节点的查找,同时进行路径压缩优化
queryBlockSum
可理解为检查当前图中有几个连通分量,可通过空间换时间实现- 新增变量
blockSum
记录当前连通分量的数量;初始为0,并在addPerson
和addRelation
时进行维护
- 新增变量
bug分析
- 本次作业由于对并查集理解不到位,导致在
addRelation
时对根节点的合并有误,从而导致强测的qci和qbs出现问题- 在
MyNework
类中新增level
数组记录各结点的深度,合并根节点时将深度更大结点的根节点作为共同根节点
- 在
第二次作业
第二次作业新增Message
类和两个与之相关的异常类,同时新增Network
类中的若干方法
算法分析
第二次作业中新增的复杂度较高的指令是qgvs和qlc,涉及的方法分别是queryGroupValueSum
和queryLeastConnection
queryGroupValueSum
即查询指定group中的value和,同样可采用空间换时间实现- 在Network类中设置
valueSum
变量,并在addPerson
,delPerson
和addRelation
时进行维护
- 在Network类中设置
queryLeastConnection
可以理解为寻找给定id的结点所在连通分量的最小生成树,并返回权值之和,可以用堆优化的prim算法实现- 采用了Java中的优先队列容器PriorityQueue存储各结点的distance(新建
PerosnDist
类作为容器存储对象),优先队列可省去一层遍历最小距离的循环;其余即实现prim算法部分
- 采用了Java中的优先队列容器PriorityQueue存储各结点的distance(新建
bug分析
- 本次作业由于
MyNetwork
类中的getPerson
方法采用了一层循环实现,prim算法又在循环中调用了getPerson
方法,导致有强测有两个点的qlc出现CTLE- 修改HashMap的查询直接使用
containskey
方法 - 各类中较简单的方法在第一次作业实现时没有考虑时间问题,第二次作业完成时又忽略了这些简单方法存在的问题而直接调用。对于各方法都应尽可能控制复杂度并在写代码的时候时刻注意复杂度是否过高
- 修改HashMap的查询直接使用
第三次作业
第三次作业新增继承自Message
的EmojiMessage
、RedEnvelopeMessage
、NoticeMessage
以及与之相关的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规则细节繁琐时,很容易出现疏漏,而一个小错误造成的后果是不可估量的。