面向对象程序设计第三单元总结

第三单元博客作业

一、自测过程

在自测之间,我一般会反复阅读自己的代码,静态地看一下可能的问题。

  • 在第一次作业的时候,尝试使用了JUnit 来构造数据,但是发现构造的时候非常的麻烦,基本上是手动构造。
  • 之后就开始使用对拍的方法来测试了……效果似乎还不错。

二、本单元的架构设计

在本单元中,所谓架构主要是指如何保存信息,JML只是指出了一种抽象的概念框架,但是在实际上可以有不同的选择,在不同的实际实现之间选择最适合本单元测试的,这是一种权衡。

例如在一个NetWork中,需要去用id去索引 MyPersonMyNetwork等元素。在JML提供的抽象概念中,使用了一个数组来表示这样的概念,由于Id并不是顺序设置的,因此有可能输入了一个很大的数,使用数组实现就不太实际了。此时我们想到可以使用ArrayList,可是这个数据结构在索引的时候需要顺序查询,时间复杂度还是偏高。

描述这样的键值对关系,一个更好的办法是使用HashMap来进行处理,由于哈希表的性质,查询变得非常的快,并且可以提供比数组更好的功能。

综上所述,在本单元中,对于所有的需要索引来查询的对象,都使用HashMap来保存。

具体而言:

  • MyNetwork中:

    • people 保存该网络中的所有id => Person 键值对
    • groups 保存该网络中的所有id => group 键值对
    • messages 保存该网络中的所有 id => Message 键值对
    • emojiHeat 保存该网络中的 emojiId => emojiHeat 键值对
  • MyPerson中:

    • edges 保存该人与其他人的所有边关系,仍然以键值对的方式存储Integer => edges

在以上的大多数使用HashMap来保存图的架构中,还有一个例外:对于每个人收到的Message,由于我们在输出的时候需要按照下标来输出,因此必须真的顺序存储,所以使用了一个ArrayList<Message>来进行存储。


在存储之后,就是维护策略:

对于大多数“添加”的行为,可以使用HashMap简单的put操作来执行维护,只需要更新id在哈希表中对应的结果就可以了。

但有少数比较Trick的地方,比如在关于Group的getValueSum的时候,需要进行一些特殊的保存,这些将在下一节中提到。

三、性能问题和修复情况

在本单元中,大多数查询对性能的要求都不高,但是有一些是例外的。

3.1 group::getValueSum

这里的问题是,如果像JML的描述一样使用二重循环来扫描的话,理论的时间复杂度就会达到O(N^2),即使强测没有数据卡这个点,互测肯定会。

我的方法是,语气让少数指令的时间复杂度达到平方,不如让所有指令的时间复杂度均摊为线性O(N).

实际上,对于每个Person而言,维护了一个哈希表

private final HashMap<Integer, Integer> groupSum;

这个哈希表的意义在于,建立一个从groupId,到“该Group中,所有与本人相连的边的权重的总和”,在添加边的时候,时间复杂度从O(1)变成了O(# Groups),但是在执行求组内边权重和的时候,只需要将组内所有的Person,在该Group下的groupSum.get(groupId)求和即可。

3.2 并查集

在本次作业中,需要判断某些Person(以下等同于点)之间是否有边相连,如果点在同一个联通块中就算做一个Block,并且还需要计算所有Block的数量。

这是一个图上分组的问题,用并查集(再加上路径优化)就很合适。

3.3 最小生成树

在第二次作业中,queryLeastConnection需要求“在某个点所在的Block中的最小生成树”,这里执行一个Kruskal算法就可以了。

3.4 Dijkstra算法的堆优化

似乎在时间复杂度上,并不需要堆优化……

总之,在Dijkstra算法中,每次需要寻找一个与当钱“求出了最短距离”的点集合中,距离最近的那一个点,这里本身是用扫描进行的,但是可以通过使用一个堆来更快地确定这个最小值。

四、Network的扩展

我的想法是,Advertiser自己可以拥有一个广告,然后还拥有一些目标人群,每次调用发送方法,广告者就把自己的广告发送给目标人群。

Class Advertiser extends Person {
    
    // public instance model non_null AdMessage ad;
    // public instance model non_null Customer[] customers;

    /** 将某个人加入到广告发送组中 */
    public void addPeson(Person p);
    
    /** 将自己的广告发送给广告发送组中的每个人*/
    public void sendAds();
}
/*
JML of function sendAds():
@ assignable customers[*].getmessages()
@ ensures customers.length == \old(customer.length)
@ ensures(\forall int i; 0 <= i && i < customers.length;customers[i].getmessages().size() == \old(customers[i].getmessages().size()) + 1))
@ ensures(\forall int i; 0 <= i && i < customers.length; 
	(\forall int j; 0 <= j && j < \old(customers[i].getmessages().size());
	\old(customers[i].getmessages().get(j)) == customers[i].getmessages().get(j+1)))
@ ensures(\forall int i; 0 <= i && i < customers.length; 
	customers[i].getmessages.get(0) == ad)
*/

对于生产商,它应该能够动态地添加商品,还可以查询商品的销售额,对于销售路径,可以用一个Map来表示:即每个广告商提供了多少销售量。

Class Producer extends Person {
    
    private ArrayList<int> productId;
    private ArrayList<int> productSale;
    
    /** 生产商可以增加自己能生产的商品*/
    public void addProduct(int id);
    
    /** 生产商可以查询某种商品的销售额*/
    public void querySale(int id) throws noProductException;
    
    /** 生产商可以查询某种商品的销售路径*/
    public Map<Advertiser, int> querySalePath(int);
    
    /** 生产商将自己接收到的最早一笔订单进行处理,更新元数据*/
    public void processOrder();
}
/*
JML of function querySale

@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < productId.size(); id == productId[i])
@ ensures (\exist int i; 0 <= i && i < productId.size(); id == productId[i] && \result == productSale[i])

@ also
@ public exceptional_behavior
@ requires (\forall int i; 0 <= i && i < productId.size(); id != productId[i])
@ signals noProductException;
*/

对于消费者,它应该能够拥有自己的偏好,还可以选择收到的广告进行购买。

Class Customer extends Person {
    
    private ArrayList<int> perference;
    private OrderMessage order;
    
    /** 消费者的偏好是否和广告匹配*/
    public void boolean(AdMessage);
    
    /** 消费者可以添加自己的偏好*/
    public void addPreference();
    
    /** 消费者选择自己收到的广告中,最新的一条符合自己偏好的广告并进行购买*/
    public void buy() throws noAdvertisementException;
}

/*
JML of function buy()
@ public normal_behavior
@ assgiable messages
@ requires (\exists int i; 0 <= i && i < getmessages().size(); getmessages().get(i) instanceof AdMessage && preference.match(get.messages().get(i)))

@ ensures getmessages.size() == \old(getmessages.size()) - 1
@ ensures (\exist int i; 0 <= i && i < \old(getmessages.size());
	\old(getmessages.get(i) instanceof AdMessage) 
	&&
	preference.match(get.messages().get(i)))
	&&
	(\forall int j; 0 <= j && j < \old(getmessages.size()); (\old(getmessages.get(j)) instanceof AdMessage && preference.match(get.messages().get(j)))) ==> (i <= j))
	&&
	(\forall int j; 0 <= j && j < \old(getmessage.size()); (j != i) ==> (\exists int k; 0 <= k && k < getmessage.size(); getmessages.get(k) == \old(getmessages.get(j)))
	&& 
	(\forall int j; 0 <= j && j < getmessage.size(); getmessages.get(j) != getmessages.get(i))
	&&
	getmessages.get(i).producer.getmessages().size() == \old(getmessages.get(i).producer.getmessages().size() + 1
	&&
	(\forall int j; 0 <= j && j < \old(getmessages.get(i).producer.getmessages().size());
	\old(getmessages.get(i).producer.getmessages().get(i)) == getmessages.get(i).producer.getmessages().get(i+1))
	&&
	getmessages.get(i).producer.getmessages().get(0) == order
)


@ also
@ public exceptional_behavior
@ requires (\forall int i; 0 <= i && i < getmessages().size(); !(getmessages().get(i) instanceof AdMessage)))
@ signals noAdvertisementException;

*/

五、学习体会

一开始,我以为JML和普通的Docs是很像的,在学习之后发现,Docs是要简洁很多,而JML可以形式化地给出函数和类的执行要求。

很多时候,在类似的情景,比如OS中看不清楚出题人的意图的时候,真的很想在函数上看到类似于JML的文档,这也说明了(我的理解中)JML的好处:它提供了一个非常清晰的前件、后件的描述。

但是这也是一种权衡,在简洁性和清晰度之间的选择。例如,在最小生成树的那个函数中,JML占了数十行,仅仅是阅读理解JML的意思就需要很久,但是普通的Docs却可以直接描述出来。

我自己在课外了解中发现,JML的使用并不像想象中的广泛,或许在实际的工业产品中,代码的各个变量、组件之间远远比现在的还要复杂,如果要想把所有的函数的效果全部用JML描写的话,会带来巨大的复杂性,这似乎就有点得不偿失了。

总结起来:在本单元中,我们了解了这样一种新的,可以执行规模化设计的语言,可以提高代码的逻辑严密性和可维护性。但在写自己的程序的时候、或是工作中,什么时候需要使用JML,还是值得三思。

posted @ 2022-06-01 12:11  RuiLinWho  阅读(39)  评论(0编辑  收藏  举报