面向对象第三章总结

面向对象第三章总结


准备测试数据

本单元引入了Junit单元测试,可以针对每个方法自己构造数据测试。但是由于其使用起来需要手动针对每个方法写判断前提和结果约束,相当麻烦,因而关于Junit我便浅尝辄止了。类似之前单元的测试,本单元我还是通过随机生成数据进行测试。

方法的JML规格约束了方法的前提与产生的结果,因此针对方法的测试可以以JML位基础,约束生成数据的边界条件,保证数据合法且能覆盖所有范围。针对数据的JML约束在本单元JML规格中似乎没有体现,相关的约束条件都在指导书中给出。类满足的不变式在满足各个方法约束的前提上可以满足,因而也不需要刻意测试。因此,自己构造数据的主要目的是针对方法进行测试。

我在编写生成数据的代码时,考虑到需要针对方法进行测试,因而为每条指令赋予了一定的比例:均衡的比例可以测试各条指令均有分布的情况;刻意调大某些指令的比例可以一定程度上对其针对性测试。但是仅仅靠随机测试远远不够,因为本单元在图上运行,很多情况下需要构造特定形状的图针对特定的指令进行特定的测试,因此自己构造数据必不可少。比如第一次作业针对连通块的数据需要自己构造最坏情况,即每人属于一个独立的连通块,然后反复查找遍历,如果有同学设计实现不好便会超时。再比如第二次第三次针对生成树和最短路的数据更需要额外构造,但是由于这两类指令数受到限制,因而正常情况下都不会因此超时。


架构设计

本单元由于主要训练对JML的理解,对层次设计的考察相对弱。因此我在本单元的实现层次结构完全按照官方包给出的接口实现,具体实现也是根据官方JML完成。

值得一提的式几个算法,并查集、最小生成树和最短路,分别应用于三次作业。关于并查集的实现我加入了路径压缩,因为官方体贴地限制了加入人的指令条数,不会被卡爆栈。最小生成树和最短路我分别使用了Prim和Dijkstra的堆优化版本,两者实现起来也只有几行的区别,因而我在一个方法中实现,通过传入类型参数区分两者。

其次是容器的选择,由于需要通过id找到下层对象,因而我尽量选择HashMap管理,这样可以在需要寻找id对应的对象时以很低的时间复杂度完成。很多同学按照JML约束给的类似遍历方法用ArrayList管理对象,寻找时遍历容器,这样增大了超时被hack的风险。


Network扩展

classDiagram class Product class Advertiser { -producers:HashMap~Product, Producer~ -customers:List~Customer~ +newCustomer(customer:Customer) +newProducer(producer:Producer) +advertiseForProducer() +buyForCustomer() } class Producer { -advertisers:List~Advertiser~ -orders:List~Product~ +advertiseProduct(product:Product) +newOrder(product:Product) } class Customer { -products:HashMap~Product, Advertiser~ +buyProduct(product:Product):void +contains(product:Product):boolean } Person <|-- Advertiser Person <|-- Producer Person <|-- Customer
/*@ public normal_behavior
  @ requires product != null;
  @ assignable customers;
  @ ensures (\forall int i; 0 <= i && i < customers.length; customers[i].contains(product));
  @ ensures (\forall int i; 0 <= i && i < customers.length;
  			\old(customers[i].contains(product)) ==> 
  			customers[i].product.length == \old(customers[i].product.length));
  @ ensures (\forall int i; 0 <= i && i < customers.length;
  			!\old(customers[i].contains(product)) ==> 
  			customers[i].product.length == \old(customers[i].product.length) + 1);
  @ ensures (\forall int i; 0 <= i && i < customers.length;
  			(\forall int j; 0 <= j && j < \old(customers[i].products.length); 
  			(\exists int z; 0 <= z && z < customers[i].products.length; customers[i].products[z] == \old(customers[i].products[j]))));
  public void advertiseForProducer(Product product);
  
/*@ public normal_behavior
  @ requires product != null;
  @ assignable producers;
  @ ensures (\forall int i; 0 <= i && i < producers.length; 
  			products[i] == product ==> producers[i].order[\old(producer[i].order.length)] == product);
  @ ensures (\forall int i; 0 <= i && i < producers.length;
  			(\forall int j; 0 <= j && j < \old(producers[i].order.length);
  			producers[i].order[j] == \old(producers[i].order[j])));
  public void buyForCustomer(Product product);
  
/*@ public normal_behavior
  @ requires product != null;
  @ assignable orders;
  @ ensures order[order.length] == product;
  @ ensures (\forall int i; 0 <= i && i < \old(order.length);
  			order[i] == \old(order[i]));
  public void newOrder(Product product);

学习体会

规格在软件开发中具有不可忽视的地位。使用规格,不仅可以方便描述对类、方法和数据的约束,而且可以消除自然语言表达的歧义,便于不同开发人员之间的合作。JML是规格的一种具体实现,在本单元学习中可以明显感受到JML的语法较为简单,且能实现的语义丰富,理解语法后读起来也很顺畅。

关于具体作业方面的体会就是不能完全按照规格描述的去实现,而是要关注性能。规格只是一种约束,只要实现的效果满足约束即可。

posted @ 2022-06-03 22:04  夜光WAN  阅读(43)  评论(0编辑  收藏  举报