OO第三单元总结
OO第三单元总结
一,梳理架构设计
1.1架构梳理
本单元主要任务是通过建设一个社交网络,来帮助我们理解jml
格,同时实现一些图的基本算法。这个社交网络经历了三次作业的迭代,这三次作业中,该社交网络的主要的架构如下:
1.1.1第一次作业
- 实际上是一个无向加权图
- 人
person
是图中的基本节点。 - 每个
person
类保存其属性,包括其id
(具有唯一性,可以用来进行区分'person'),以及name
,age
等基本属性。 - 每个
person
类用一个数组acquaintance
保存其邻接节点,同时附带一个数组value
保存其与它的邻接节点的加权距离。 - 存在组群
group
,每个组中有一个person
的数组,其形式相当于群聊。 - 实现了图
Network
保存所有节点,即person
,以及所有的群组group
。 - 图
Network
实现了用来维护图的基本操作,包括增加节点,为两个节点添加关系等。 - 图
Network
实现了对组的维护,包括添加组,为组添加或者删除人等。 - 本次作业的难点:
isCircle
,即查询两个节点是否连通。
1.1.2第二次作业
第二次作业在第一次作业的基础上有了一定的拓展,主要体现在一下几个方面:
- 新增
message
类,是人与人之间的信息,有其基本属性,可以用其基本属性type
来区分是人对人的消息还是人对组的消息。 - 在Network中实现信息发送。
- 本次作业难点:
queryLeastConnection
:是求最小生成树的加权值。
1.1.3第三次作业
第三次作业主要是对message
的扩展,实现不同的消息:
- 新增
emojiMessage
:表情包,RedEnvelopeMessage
:红包,发送时更改发送人和接收人的money
属性,noticeMessage
:发送的消息。 - 其中对于
emojiMessage
,其发送的表情emoji
需要在发送时判断表情是否已经被储存。于是,Network
类需要实现对于表情包的管理。同时本次作业还实现了对冷门表情包的删除 - 本次作业的难点在于
sendIndirectMessage
,这个方法要实现传送间接消息,实际上是求最短路径。
1.2图模型建构和维护策略。
本次作业中,Person
是图的节点。每个person
类用一个数组acquaintance
保存其邻接节点,同时附带一个数组value
保存其与它的邻接节点的加权距离。而group
类其实在图中的作用并不明显。有关图的算法,包括求连通性,最小生成树等都和group
没有太大关联,所以在考虑图模型时可以不用加以考虑。而对于各类Message
来说,它们对于图的作用主要是在发送信息时要在图中查询图的信息,包括节点的连通性等。但是它们也不会修改图,所以不会影响图的建构。所以在维护图时,主要要关注的就是对于person
的增删和关系的添加。
二,性能问题和修复情况。
在这三次作业之中,每次作业都涉及到一个图的算法问题,这也是每次作业的难点所在
第一次作业
这次作业中要实现的isCircle
算法。这个算法是给出两个Person
的id,然后查询这两个节点在图中是否连通。
我的实现方法是利用并查集方法。并查集的思路是:每一个相连通的节点都在同一个连通分支中,而连通分支就必然有一个生成树,我们只需要为每一个节点维护一个父节点,则在同一个连通分支中的节点都有着同一个父节点。这样我们在查询两个人之间是否有关联时,只需要查询两个人的父节点是否相同即可。具体的代码段如下:
public boolean isCircle(int id1, int id2) throws PersonIdNotFoundException {
if (!this.contains(id1)) {
throw new MyPersonIdNotFoundException(id1);
} else if (!this.contains(id2)) {
throw new MyPersonIdNotFoundException(id2);
} else {
return this.queryBoot(id1) == this.queryBoot(id2) || id1 == id2;
}
}
public int queryBoot(int id)
{
int fatherId;
fatherId=id;
while(fatherId!=getPerson(fatherId).getFather())
{
fatherId=getPerson(fatherId).getFather();
}
getPerson(id).setFather(fatherId);
return fatherId;
}
在这次的实现中,我犯了反复查询的问题。即在一次查询之后没有将每个节点的树根保存,导致不断查询,最后更改了这一问题,性能有所上升。
第二次作业
第二次作业主要的难点在于queryLeastConnection
,这个方法是要实现寻找包含某个节点的最小生成树并返回生成树的路径长度之和。我使用的是prim算法去实现最小生成树。对图G(V,E)设置集合S,存放已访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。执行n次(n为顶点个数),直到集合S已包含所有顶点。
具体的实现方法如下:
public int queryLeastConnection(int id) throws PersonIdNotFoundException {
if (!contains(id)) {
throw new MyPersonIdNotFoundException(id);
} else {
ArrayList<Person> in = new ArrayList<>();
ArrayList<Person> out = new ArrayList<>();
Person boot = getPerson(id);
out.add(boot);
for (Person person : people) {
if (isCircle(person.getId(), id) && person.getId() != id) {
out.add(person);
}
}
int minTree = 0;
in.add(out.get(0));
out.remove(0);
while (out.size() != 0) {
int minValue = 9999999;
int addWhich = -1;
for (int i = 0; i < out.size(); i++) {
Person person = out.get(i);
for (Person person1 : in) {
if (person.isLinked(person1)) {
if (person.queryValue(person1) < minValue) {
minValue = person.queryValue(person1);
addWhich = i;
}
}
}
}
in.add(out.get(addWhich));
out.remove(addWhich);
minTree = minTree + minValue;
}
return minTree;
}
}
第三次作业
第三次作业中的难点主要在于sendIndirecetMessage
,这个方法要求出两个节点的最短路径,求出最短路径之后传送消息并返回最短路径的长度。我的实现方法是利用Dijkstra算法。具体的实现如下:
while (addWhich!=to.getId())
{
if(U.size()==1)
{
for(MyPerson myPerson :S)
{
if(myPerson.isLinked(from))
{
myPerson.setValueInDjs(myPerson.queryValue(from));
}
}
}
else {
for (MyPerson myPerson : S) {
if (myPerson.isLinked(getPerson(addWhich))) {
if (getPerson(addWhich).getValueInDjs() + getPerson(addWhich).queryValue(myPerson) <= myPerson.getValueInDjs()) {
myPerson.setValueInDjs(getPerson(addWhich).getValueInDjs() + getPerson(addWhich).queryValue(myPerson));
}
}
}
}
int min=99999;
for (MyPerson myPerson : S) {
if (myPerson.getValueInDjs() < min) {
min = myPerson.getValueInDjs();
addWhich = myPerson.getId();
}
}
U.add(getPerson(addWhich));
S.remove(getPerson(addWhich));
}
三,jml扩展以及相应规格
扩展编写
题目:
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等
扩展Person类
继承Person
接口实现社交网络中不同的角色;
public interface Advertiser extends Person {
}
public interface Producer extends Person {
//@ public instance model int cost; 商品的成本
//@ public instance model int [] productNum; 剩余商品的数量
//@ public instance model String [] productName;剩余商品名
//@ ensures \result == productNum;
public /*@ pure @*/ int getProductNum();
/*@ public normal_behavior
@ assignable socialValue;
@ ensures money == \old(money) - cost;
@ ensures productNum == \old(productNum) + 1;
@*/
public int produce();
}
public interface Customer extends Person {
//@ public instance model non_null int[] preferenceId; 喜好商品的id
//@ public instance model non_null int[] ownProductId; 已经购买的商品id
}
规定新的规则以及Message
类
AdvertisementMessage
:广告信息,由广告发布者发送给消费者,同时消费者心愿清单中会增加当前被宣传的商品。BuyProductMessage
:购买信息,由消费者向生产者发出,发出之后消费者购买物品,更新购买者的心愿单,包含物品单以及生产的剩余物品。
给出这两个类的jml规定:
public interface AdvertisementMessage extends Message {
//@ public instance model int productId; 广告宣传的商品的id
//@ public instance model int money;广告消息的成本
//@ ensures \result == productId;
public /*@ pure @*/ int getProductId();
//@ ensures \result == money;
public /*@ pure @*/ int getMoney();
}
public interface ProductMessage extends Message {
//@ public instance model int producterId;生产商Id
//@ public instance model int money; 商品的购买价格
//@ ensures \result == money;
public /*@ pure @*/ int getMoney();
//@ ensures \result == productId;
public /*@ pure @*/ int getProductId();
}
核心业务功能实现
生产者生产商品:生产某个新的产品
/*@ public normal_behaviour
@ requires !producer.hasProduct(product);
@ assignable producer.products;
@ ensures producer.products.length = \old(producer.products.length) + 1;
@ ensures producer.hasProduct(product);
@ ensures (\forall int i; 0 <= i && i < \old(producer.products.length);
@ (\exists int j; 0 <= j && j < producer.products.length;
@ producer.productName[i] .equals (\old(producer.productName[j]))));
@ ensures (\forall int i; 0 <= i && i < \old(producer.products.length);
@ (\exists int j; 0 <= j && j < producer.products.length;
@ producer.productName[i] .equals (\old(producer.productName[j]))&&
producer.productNum[i]==(\old(producer.productNum[j])+1))));
@ also
@ public exceptional_behaviour
@ signals (DuplicatedProductException e) producer.hasProduct(product);
@*/
void produce(Producer producer, String product);
实现广告信息的发送:发送广告信息,在原有的sendMessage
的基础上,更新消费者的'preferenceId'列表。同时,在要推荐的商品不存在时,会丢出ProductIdNotFoundException
的异常。
/*@ public normal_behavior
@ requires containsAdvertisementMessage(id) && getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2() &&
@ getMessage(id).getPerson1().getMoney() < getMessage(id).getMoney() &&
@ contains(productId);
@ assignable messages, advertisementMessages;
@ assignable getMessage(id).getPerson1().socialValue;
@ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().socialValue;
@ assignable getMessage(id).getPerson1().money;
@ assignable getMessage(id).getPerson2().preferenceId;
@ ensures \old(getMessage(id)).getPerson1().getSocialValue() ==
@ \old(getMessage(id).getPerson1().getSocialValue()) +
@ \old(getMessage(id)).getSocialValue() &&
@ \old(getMessage(id)).getPerson2().getSocialValue() ==
@ \old(getMessage(id).getPerson2().getSocialValue()) +
@ \old(getMessage(id)).getSocialValue();
@ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
@ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
@ ensures !containsAdvertisementMessage(id) && advertisementMessages.length ==
@ \old(advertisementMessages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(advertisementMessages.length) &&
@ \old(advertisementMessages[i].getId()) != id;
@ (\exists int j; 0 <= j && j < advertisementMessages.length;
@ advertisementMessages[j].equals(\old(advertisementMessages[i]))));
@ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
@ \old(getMessage(id)).getPerson2().getMessages().get(i+1) ==
@ \old(getMessage(id).getPerson2().getMessages().get(i)));
@ ensures \old(getMessage(id)).getPerson2().getMessages().get(0) == \old(getMessage(id));
@ ensures \old(getMessage(id)).getPerson2().getMessages().size() ==
@ \old(getMessage(id).getPerson2().getMessages().size()) + 1;
@ ensures \old(getMessage(id)).getPerson2().getMoney() ==
@ \old(getMessage(id).getPerson2().getMoney()) - \old(getMessage(id)).getMoney;
@ ensures getMessage(id).getPerson2().preferenceId.length ==
@ \old(getMessage(id).getPerson2().preferenceId.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().preferenceId.length);
@ (\exists int j; 0 <= j && j < getMessage(id).getPerson2().preferenceId.length;
@ getMessage(id).getPerson2().preferenceId[j] == (\old(people[i]))));
@ ensures (\exists int i; 0 <= i && i < getMessage(id).getPerson2().preferenceId.length; people[i]
@ == getMessage(id).getProductId());
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !containsAdvertisementMessage(id);
@ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id).getType() == 0 &&
@ !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
@ signals (ProductIdNotFoundException e) !contains(productId);
@*/
public void sendAdvertisementMessage(int id) throws
RelationNotFoundException, MessageIdNotFoundException,
ProductIdNotFoundException;
实现商品的购买:让某个消费者从某个生产者购买商品:
/*@ public normal_behaviour
@ requires advertiser.hasProduct(product) && !customer.hasProduct(product);
@ assignable customer.products, advertiser.products;
@ assignable customer.money, product.getProducer().money;
@ ensures customer.products.length = \old(customer.products.length) + 1;
@ ensures customer.hasProduct(product);
@ ensures (\forall int i; 0 <= i && i < \old(customer.products.length);
@ (\exists int j; 0 <= j && j < customer.products.length;
@ customer.products[j] == (\old(customer.products[i]))));
@ ensures advertiser.products.length = \old(advertiser.products.length) - 1;
@ ensures !advertiser.hasProduct(product);
@ ensures (\forall int i; 0 <= i && i < advertiser.products.length;
@ (\exists int j; 0 <= j && j < \old(advertiser.products.length);
@ advertiser.products[i] == (\old(advertiser.products[j]))));
@ ensures customer.money = customer.money - product.getPrice();
@ ensures product.getProducer().money = product.getProducer().money + product.getPrice();
@ also
@ public exceptional_behaviour
@ signals (ProductNotFoundException e) !advertiser.hasProduct(product);
@ also
@ public exceptional_behaviour
@ signals (DuplicatedProductException e) advertiser.hasProduct(product) && customer.hasProduct(product);
@*/
void purchase(Advertiser advertiser, Customer customer, ProductMessage product);
四,心得体会
这个单元的作业是根据jml规格来实现一个社交网路,总的来说,相对前两个单元,任务量有所降低。主要的难点在与理解jml规格。我的理解是,在解读比较复杂的jml规格时,先要大致浏览,明白jml要实现的是什么方法,有没有对于这种方法的更具体的归纳。例如在第二单元的作业中,对于queryLeastConnect
方法,一开始我实在没有读懂要干什么。在看了讨论区后,才明白它是要查询最小生成树。这之后才慢慢对它的规格进行具体的分析,最后的实现也比较成功。还有就是,在这个单元的学习中,我对接口和继承的理解更加深入了。因为在本单元中出现了接口继承接口,而又要对它们分别是实现的情况。在这种情况下,子接口的实现方法是不能直接继承父接口的实现方法的。一开始我还不太理解,通过这次具体的代码编写,我对这些知识的理解也更加深刻了。